pentesting-checklist-guide

v2026.09.24

Interactive security assessment checklist covering 23 platforms with 1000+ checks for penetration testing, bug bounty, and security audits

GitHub
Install command
npx skhub add reason-machines/pentesting-checklist-guide
Markdown
SKILL.md

Pentesting Checklist Guide

Skill by ara.so — Security Skills collection.

Overview

PentestingChecklist is a comprehensive, interactive security assessment framework covering 23 platforms (Web, API, Mobile, Cloud, Active Directory, Kubernetes, LLM, and more) with over 1,000 security checks. Built in TypeScript with React, it runs entirely client-side with no backend—all data persists in browser localStorage.

Key capabilities:

  • Hierarchical checklist structure: Platform → Category → Technology → Check
  • Global search across all checks, descriptions, tags, and references
  • Progress tracking with per-check status (Open/Closed/N/A)
  • Notes and findings capture
  • Export to Markdown, CSV, Excel, or JSON
  • Import/export for assessment portability

Live deployment: https://checklist.m14r41.in/

Project Structure

src/
├── data/
│   ├── platforms/           # 23 platform checklist definitions
│   │   ├── web.ts
│   │   ├── api.ts
│   │   ├── mobile.ts
│   │   ├── cloud.ts
│   │   ├── active-directory.ts
│   │   └── ...
│   └── types.ts            # Core data models
├── components/
│   ├── Checklist.tsx       # Main checklist component
│   ├── ChecklistItem.tsx   # Individual check rendering
│   ├── Search.tsx          # Global search (⌘K/Ctrl+K)
│   ├── Export.tsx          # Export functionality
│   └── ProgressBar.tsx     # Progress tracking
├── hooks/
│   ├── useLocalStorage.ts  # Browser persistence
│   └── useProgress.ts      # Progress calculation
└── utils/
    ├── export.ts           # Export formatters
    └── search.ts           # Search indexing

Data Model

Core Types

// src/data/types.ts

export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';

export type CheckStatus = 'open' | 'closed' | 'na' | 'unchecked';

export interface Check {
  id: string;
  title: string;
  description: string;
  severity: Severity;
  tags: string[];
  tools?: string[];
  references?: string[];
}

export interface Technology {
  id: string;
  name: string;
  checks: Check[];
}

export interface Category {
  id: string;
  name: string;
  description: string;
  technologies: Technology[];
}

export interface Platform {
  id: string;
  name: string;
  slug: string;
  description: string;
  icon: string;
  categories: Category[];
}

export interface CheckState {
  status: CheckStatus;
  notes: string;
  timestamp: number;
}

export interface AssessmentData {
  [checkId: string]: CheckState;
}

Adding a New Platform

Step 1: Create Platform Definition

// src/data/platforms/example-platform.ts

import { Platform } from '../types';

export const examplePlatform: Platform = {
  id: 'example-platform',
  name: 'Example Platform',
  slug: 'example-platform',
  description: 'Security assessment checklist for Example Platform',
  icon: '🔒',
  categories: [
    {
      id: 'authentication',
      name: 'Authentication',
      description: 'Authentication and session management checks',
      technologies: [
        {
          id: 'oauth',
          name: 'OAuth 2.0',
          checks: [
            {
              id: 'oauth-redirect-validation',
              title: 'Validate OAuth redirect_uri parameter',
              description: 'Verify that redirect_uri is strictly validated against a whitelist to prevent open redirect vulnerabilities',
              severity: 'high',
              tags: ['oauth', 'open-redirect', 'authorization'],
              tools: ['Burp Suite', 'OWASP ZAP'],
              references: [
                'https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1',
                'https://owasp.org/www-community/attacks/OAuth_2_0_Open_Redirect'
              ]
            },
            {
              id: 'oauth-state-parameter',
              title: 'Check for CSRF protection via state parameter',
              description: 'Ensure OAuth state parameter is used and properly validated to prevent CSRF attacks',
              severity: 'high',
              tags: ['oauth', 'csrf', 'session'],
              tools: ['Burp Suite'],
              references: [
                'https://datatracker.ietf.org/doc/html/rfc6749#section-10.12'
              ]
            }
          ]
        }
      ]
    },
    {
      id: 'authorization',
      name: 'Authorization',
      description: 'Access control and privilege escalation checks',
      technologies: [
        {
          id: 'rbac',
          name: 'Role-Based Access Control',
          checks: [
            {
              id: 'rbac-horizontal-authz',
              title: 'Test for horizontal authorization bypass',
              description: 'Verify users cannot access resources belonging to other users at the same privilege level',
              severity: 'critical',
              tags: ['idor', 'authz', 'access-control'],
              tools: ['Burp Suite', 'Autorize extension'],
              references: [
                'https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/05-Authorization_Testing/02-Testing_for_Bypassing_Authorization_Schema'
              ]
            }
          ]
        }
      ]
    }
  ]
};

Step 2: Register Platform

// src/data/platforms/index.ts

import { Platform } from '../types';
import { webPlatform } from './web';
import { apiPlatform } from './api';
import { examplePlatform } from './example-platform';

export const platforms: Platform[] = [
  webPlatform,
  apiPlatform,
  examplePlatform,
  // ... other platforms
];

export const getPlatformBySlug = (slug: string): Platform | undefined => {
  return platforms.find(p => p.slug === slug);
};

Working with Assessment Data

Saving Check Status

// src/hooks/useLocalStorage.ts

import { useState, useEffect } from 'react';
import { AssessmentData, CheckState } from '../data/types';

const STORAGE_KEY = 'pentesting-checklist-data';

export function useAssessmentData() {
  const [data, setData] = useState<AssessmentData>(() => {
    const stored = localStorage.getItem(STORAGE_KEY);
    return stored ? JSON.parse(stored) : {};
  });

  useEffect(() => {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
  }, [data]);

  const updateCheck = (checkId: string, state: Partial<CheckState>) => {
    setData(prev => ({
      ...prev,
      [checkId]: {
        ...prev[checkId],
        ...state,
        timestamp: Date.now()
      }
    }));
  };

  const clearAllData = () => {
    setData({});
    localStorage.removeItem(STORAGE_KEY);
  };

  return { data, updateCheck, clearAllData };
}

Using Assessment Data in Components

// src/components/ChecklistItem.tsx

import React, { useState } from 'react';
import { Check, CheckState } from '../data/types';

interface ChecklistItemProps {
  check: Check;
  state?: CheckState;
  onUpdate: (checkId: string, state: Partial<CheckState>) => void;
}

export function ChecklistItem({ check, state, onUpdate }: ChecklistItemProps) {
  const [notesOpen, setNotesOpen] = useState(false);
  const [notes, setNotes] = useState(state?.notes || '');

  const handleStatusChange = (status: CheckState['status']) => {
    onUpdate(check.id, { status });
  };

  const handleNoteSave = () => {
    onUpdate(check.id, { notes });
    setNotesOpen(false);
  };

  const severityColors = {
    critical: 'bg-red-100 text-red-800',
    high: 'bg-orange-100 text-orange-800',
    medium: 'bg-yellow-100 text-yellow-800',
    low: 'bg-blue-100 text-blue-800',
    info: 'bg-gray-100 text-gray-800'
  };

  return (
    <div className="border-b p-4">
      <div className="flex items-start gap-3">
        <input
          type="checkbox"
          checked={state?.status === 'closed'}
          onChange={() => handleStatusChange(
            state?.status === 'closed' ? 'open' : 'closed'
          )}
          className="mt-1"
        />
        
        <div className="flex-1">
          <div className="flex items-center gap-2">
            <h4 className="font-medium">{check.title}</h4>
            <span className={`text-xs px-2 py-1 rounded ${severityColors[check.severity]}`}>
              {check.severity}
            </span>
          </div>
          
          <p className="text-sm text-gray-600 mt-1">{check.description}</p>
          
          {check.tags && (
            <div className="flex gap-1 mt-2">
              {check.tags.map(tag => (
                <span key={tag} className="text-xs bg-gray-100 px-2 py-1 rounded">
                  #{tag}
                </span>
              ))}
            </div>
          )}
          
          {check.tools && (
            <div className="text-xs text-gray-500 mt-2">
              🛠️ Tools: {check.tools.join(', ')}
            </div>
          )}
          
          {check.references && (
            <div className="mt-2">
              {check.references.map((ref, i) => (
                <a
                  key={i}
                  href={ref}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="text-xs text-blue-600 hover:underline block"
                >
                  📚 {ref}
                </a>
              ))}
            </div>
          )}
          
          <div className="mt-3 flex gap-2">
            <button
              onClick={() => handleStatusChange('open')}
              className={`text-xs px-3 py-1 rounded ${
                state?.status === 'open' ? 'bg-red-500 text-white' : 'bg-gray-200'
              }`}
            >
              Open
            </button>
            <button
              onClick={() => handleStatusChange('closed')}
              className={`text-xs px-3 py-1 rounded ${
                state?.status === 'closed' ? 'bg-green-500 text-white' : 'bg-gray-200'
              }`}
            >
              Closed
            </button>
            <button
              onClick={() => handleStatusChange('na')}
              className={`text-xs px-3 py-1 rounded ${
                state?.status === 'na' ? 'bg-gray-500 text-white' : 'bg-gray-200'
              }`}
            >
              N/A
            </button>
            <button
              onClick={() => setNotesOpen(!notesOpen)}
              className="text-xs px-3 py-1 rounded bg-blue-100 hover:bg-blue-200"
            >
              📝 Notes
            </button>
          </div>
          
          {notesOpen && (
            <div className="mt-3">
              <textarea
                value={notes}
                onChange={(e) => setNotes(e.target.value)}
                placeholder="Add findings, payloads, evidence..."
                className="w-full p-2 border rounded text-sm"
                rows={4}
              />
              <button
                onClick={handleNoteSave}
                className="mt-2 px-3 py-1 bg-blue-500 text-white rounded text-sm"
              >
                Save Note
              </button>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

Progress Tracking

Calculate Progress

// src/hooks/useProgress.ts

import { Platform, AssessmentData } from '../data/types';

export interface ProgressStats {
  total: number;
  checked: number;
  open: number;
  closed: number;
  na: number;
  percentage: number;
}

export function calculateProgress(
  platform: Platform,
  data: AssessmentData
): ProgressStats {
  let total = 0;
  let checked = 0;
  let open = 0;
  let closed = 0;
  let na = 0;

  platform.categories.forEach(category => {
    category.technologies.forEach(technology => {
      technology.checks.forEach(check => {
        total++;
        const state = data[check.id];
        if (state) {
          checked++;
          if (state.status === 'open') open++;
          if (state.status === 'closed') closed++;
          if (state.status === 'na') na++;
        }
      });
    });
  });

  return {
    total,
    checked,
    open,
    closed,
    na,
    percentage: total > 0 ? Math.round((checked / total) * 100) : 0
  };
}

export function calculateCategoryProgress(
  categoryId: string,
  platform: Platform,
  data: AssessmentData
): ProgressStats {
  const category = platform.categories.find(c => c.id === categoryId);
  if (!category) {
    return { total: 0, checked: 0, open: 0, closed: 0, na: 0, percentage: 0 };
  }

  let total = 0;
  let checked = 0;
  let open = 0;
  let closed = 0;
  let na = 0;

  category.technologies.forEach(technology => {
    technology.checks.forEach(check => {
      total++;
      const state = data[check.id];
      if (state) {
        checked++;
        if (state.status === 'open') open++;
        if (state.status === 'closed') closed++;
        if (state.status === 'na') na++;
      }
    });
  });

  return {
    total,
    checked,
    open,
    closed,
    na,
    percentage: total > 0 ? Math.round((checked / total) * 100) : 0
  };
}

Export Functionality

Export to Markdown

// src/utils/export.ts

import { Platform, AssessmentData, Check, CheckState } from '../data/types';

export function exportToMarkdown(
  platform: Platform,
  data: AssessmentData
): string {
  let md = `# ${platform.name} Security Assessment\n\n`;
  md += `**Date:** ${new Date().toISOString().split('T')[0]}\n\n`;
  
  const stats = calculateProgress(platform, data);
  md += `## Summary\n\n`;
  md += `- Total checks: ${stats.total}\n`;
  md += `- Completed: ${stats.checked} (${stats.percentage}%)\n`;
  md += `- Open findings: ${stats.open}\n`;
  md += `- Closed: ${stats.closed}\n`;
  md += `- N/A: ${stats.na}\n\n`;
  
  md += `---\n\n`;
  
  platform.categories.forEach(category => {
    md += `## ${category.name}\n\n`;
    md += `${category.description}\n\n`;
    
    category.technologies.forEach(technology => {
      md += `### ${technology.name}\n\n`;
      
      technology.checks.forEach(check => {
        const state = data[check.id];
        const status = state?.status || 'unchecked';
        const statusEmoji = {
          open: '🔴',
          closed: '✅',
          na: '⚪',
          unchecked: '⬜'
        };
        
        md += `#### ${statusEmoji[status]} ${check.title}\n\n`;
        md += `**Severity:** ${check.severity.toUpperCase()}  \n`;
        md += `**Status:** ${status.toUpperCase()}  \n\n`;
        md += `${check.description}\n\n`;
        
        if (check.tags.length > 0) {
          md += `**Tags:** ${check.tags.map(t => `\`${t}\``).join(', ')}  \n\n`;
        }
        
        if (check.tools && check.tools.length > 0) {
          md += `**Tools:** ${check.tools.join(', ')}  \n\n`;
        }
        
        if (state?.notes) {
          md += `**Notes:**\n\n`;
          md += `\`\`\`\n${state.notes}\n\`\`\`\n\n`;
        }
        
        if (check.references && check.references.length > 0) {
          md += `**References:**\n\n`;
          check.references.forEach(ref => {
            md += `- ${ref}\n`;
          });
          md += `\n`;
        }
        
        md += `---\n\n`;
      });
    });
  });
  
  return md;
}

export function downloadMarkdown(platform: Platform, data: AssessmentData) {
  const markdown = exportToMarkdown(platform, data);
  const blob = new Blob([markdown], { type: 'text/markdown' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `${platform.slug}-assessment-${Date.now()}.md`;
  a.click();
  URL.revokeObjectURL(url);
}

Export to JSON

export interface ExportData {
  version: string;
  platform: string;
  exportDate: string;
  data: AssessmentData;
}

export function exportToJSON(
  platform: Platform,
  data: AssessmentData
): string {
  const exportData: ExportData = {
    version: '1.0',
    platform: platform.id,
    exportDate: new Date().toISOString(),
    data
  };
  
  return JSON.stringify(exportData, null, 2);
}

export function importFromJSON(jsonString: string): ExportData {
  const imported = JSON.parse(jsonString);
  
  if (!imported.version || !imported.platform || !imported.data) {
    throw new Error('Invalid export format');
  }
  
  return imported as ExportData;
}

export function downloadJSON(platform: Platform, data: AssessmentData) {
  const json = exportToJSON(platform, data);
  const blob = new Blob([json], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `${platform.slug}-assessment-${Date.now()}.json`;
  a.click();
  URL.revokeObjectURL(url);
}

Global Search

Search Implementation

// src/utils/search.ts

import { Platform, Check } from '../data/types';

export interface SearchResult {
  check: Check;
  platformId: string;
  platformName: string;
  categoryId: string;
  categoryName: string;
  technologyId: string;
  technologyName: string;
  matchScore: number;
}

export function searchChecks(
  platforms: Platform[],
  query: string
): SearchResult[] {
  if (!query.trim()) return [];
  
  const terms = query.toLowerCase().split(' ').filter(t => t.length > 0);
  const results: SearchResult[] = [];
  
  platforms.forEach(platform => {
    platform.categories.forEach(category => {
      category.technologies.forEach(technology => {
        technology.checks.forEach(check => {
          const searchableText = [
            check.title,
            check.description,
            ...check.tags,
            ...(check.tools || []),
            platform.name,
            category.name,
            technology.name
          ].join(' ').toLowerCase();
          
          let matchScore = 0;
          terms.forEach(term => {
            if (searchableText.includes(term)) {
              matchScore++;
              // Boost score for title matches
              if (check.title.toLowerCase().includes(term)) {
                matchScore += 2;
              }
            }
          });
          
          if (matchScore > 0) {
            results.push({
              check,
              platformId: platform.id,
              platformName: platform.name,
              categoryId: category.id,
              categoryName: category.name,
              technologyId: technology.id,
              technologyName: technology.name,
              matchScore
            });
          }
        });
      });
    });
  });
  
  return results.sort((a, b) => b.matchScore - a.matchScore);
}

Search Component

// src/components/Search.tsx

import React, { useState, useEffect } from 'react';
import { searchChecks, SearchResult } from '../utils/search';
import { platforms } from '../data/platforms';

interface SearchProps {
  onResultClick: (result: SearchResult) => void;
}

export function Search({ onResultClick }: SearchProps) {
  const [isOpen, setIsOpen] = useState(false);
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<SearchResult[]>([]);

  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
        e.preventDefault();
        setIsOpen(true);
      }
      if (e.key === 'Escape') {
        setIsOpen(false);
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, []);

  useEffect(() => {
    if (query.trim()) {
      const searchResults = searchChecks(platforms, query);
      setResults(searchResults.slice(0, 20)); // Limit to 20 results
    } else {
      setResults([]);
    }
  }, [query]);

  const handleResultClick = (result: SearchResult) => {
    onResultClick(result);
    setIsOpen(false);
    setQuery('');
  };

  if (!isOpen) return null;

  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-start justify-center pt-20 z-50">
      <div className="bg-white rounded-lg shadow-xl w-full max-w-2xl">
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Search checks, platforms, categories... (⌘K)"
          className="w-full p-4 text-lg border-b focus:outline-none"
          autoFocus
        />
        
        {results.length > 0 && (
          <div className="max-h-96 overflow-y-auto">
            {results.map((result, i) => (
              <div
                key={`${result.platformId}-${result.check.id}`}
                onClick={() => handleResultClick(result)}
                className="p-4 hover:bg-gray-50 cursor-pointer border-b"
              >
                <div className="flex items-center gap-2 mb-1">
                  <span className="text-xs text-gray-500">
                    {result.platformName} → {result.categoryName} → {result.technologyName}
                  </span>
                  <span className={`text-xs px-2 py-0.5 rounded ${
                    result.check.severity === 'critical' ? 'bg-red-100 text-red-800' :
                    result.check.severity === 'high' ? 'bg-orange-100 text-orange-800' :
                    result.check.severity === 'medium' ? 'bg-yellow-100 text-yellow-800' :
                    'bg-gray-100 text-gray-800'
                  }`}>
                    {result.check.severity}
                  </span>
                </div>
                <div className="font-medium">{result.check.title}</div>
                <div className="text-sm text-gray-600 mt-1 line-clamp-2">
                  {result.check.description}
                </div>
              </div>
            ))}
          </div>
        )}
        
        {query && results.length === 0 && (
          <div className="p-8 text-center text-gray-500">
            No results found for "{query}"
          </div>
        )}
      </div>
    </div>
  );
}

Development

Local Setup

# Clone the repository
git clone https://github.com/m14r41/PentestingChecklist.git
cd PentestingChecklist

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Preview production build
npm run preview

Project Configuration

// package.json (key dependencies)
{
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.14.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.0",
    "@types/react-dom": "^18.2.0",
    "@vitejs/plugin-react": "^4.0.0",
    "typescript": "^5.0.0",
    "vite": "^4.4.0"
  }
}

TypeScript Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

Common Patterns

Filter Checks by Severity

import { Check, Severity } from '../data/types';

export function filterChecksBySeverity(
  checks: Check[],
  severities: Severity[]
): Check[] {
  if (severities.length === 0) return checks;
  return checks.filter(check => severities.includes(check.severity));
}

// Usage in component
const [selectedSeverities, setSelectedSeverities] = useState<Severity[]>([]);
const filteredChecks = filterChecksBySeverity(
  technology.checks,
  selectedSeverities
);

Get Open Findings Report

import { Platform, AssessmentData, Check } from '../data/types';

export interface Finding {
  check: Check;
  notes: string;
  categoryName: string;
  technologyName: string;
}

export function getOpenFindings(
  platform: Platform,
  data: AssessmentData
): Finding[] {
  const findings: Finding[] = [];
  
  platform.categories.forEach(category => {
    category.technologies.forEach(technology => {
      technology.checks.forEach(check => {
        const state = data[check.id];
        if (state?.status === 'open') {
          findings.push({
            check,
            notes: state.notes || '',
            categoryName: category.name,
            technologyName: technology.name
          });
        }
      });
    });
  });
  
  // Sort by severity
  const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
  return findings.sort(
    (a, b) => severityOrder[a.check.severity] - severityOrder[b.check.severity]
  );
}

Bulk Operations

// Mark all checks in a category as N/A
export function markCategoryNA(
  categoryId: string,
  platform: Platform,
  updateCheck: (checkId: string, state: Partial<CheckState>) => void
) {
  const category = platform.categories.find(c => c.id === categoryId);
  if (!category) return;
  
  category.technologies.forEach(technology => {
    technology.checks.forEach(check => {
      updateCheck(check.id, { status: 'na' });
    });
  });
}

// Clear all progress for a platform
export function clearPlatformProgress(
  platform: Platform,
  updateCheck: (checkId: string, state: Partial<CheckState>) => void
) {
  platform.categories.forEach(category => {
    category.technologies.forEach(technology => {
      technology.checks.forEach(check => {
        updateCheck(check.id, { status: 'unchecked', notes: '' });
      });
    });
  });
}

Troubleshooting

Data Not Persisting

Issue: Assessment data disappears after refresh.

Solution: Check localStorage availability and quotas:

function checkLocalStorage() {
  try {
    const test = '__storage_test__';
    localStorage.setItem(test, test);
    localStorage.removeItem(test);
    return true;
  } catch (e) {
    console.error('localStorage unavailable:', e);
    return false;
  }
}

// Check quota
if (navigator.storage && navigator.storage.estimate) {
  navigator.storage.estimate().then(estimate => {
    console.log(`Storage: ${estimate.usage
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

NOASSERTION

Source path

skills/pentesting-checklist-guide

Default branch

main

Latest commit

304c245

Tree SHA

010f47d