Home Doh Ref
Dohballs
  • 📁 doh_chat
  • 📁 doh_modules
    • 📦 dataforge
    • 📦 express
    • 📁 sso
    • 📁 user

Security Audit

A comprehensive security scanning and auditing system for Doh applications. Evaluates application security posture across authentication, server configuration, database, input validation, and permissions — with a web dashboard, CLI tool, persistent audit logging, and machine-readable manifest output.

Features

  • 26 Security Checks across 5 categories with severity-weighted scoring and letter grades (A-F)
  • Web Dashboard at /admin/security with score visualization, category breakdowns, and expandable check details
  • CLI Tool via doh audit for terminal and CI/CD integration
  • Manifest Output — every audit run writes results to .doh/manifests/security_audit.json for passive consumption by other CLI tools, AI agents, and automation
  • Audit Logging with persistent event storage and statistics
  • Autofix Support for checks that provide automated remediation
  • Extensible — register custom checks from any module
  • Permission-Based Access with separate read and write permissions

Installation

Add to your boot.pod.yaml:

host_load:
  - security_audit

Quick Start

doh audit              # Run all checks, see results, write manifest

That's it. Results are printed to the terminal and saved to .doh/manifests/security_audit.json.

Usage

CLI

doh audit                        # Run all checks
doh audit run                    # Explicit run subcommand
doh audit --category auth        # Run only auth checks
doh audit --json                 # JSON output for CI/CD
doh audit --fix                  # Run checks then apply autofixes
doh audit log                    # Show recent audit log
doh audit log --limit 100        # Show 100 recent entries
doh audit --help                 # Show help

The CLI exits with code 1 if any checks fail, making it suitable for CI/CD pipelines and pre-deploy gates.

Web Dashboard

Navigate to /admin/security. Users must be authenticated and have the access:security_audit permission.

The dashboard provides:

  • Overall score circle with letter grade
  • Category cards with pass/warn/fail counts (click to filter)
  • Expandable check rows with messages, remediation, and details
  • Paginated audit log

Manifest File

Every audit run (CLI, server API, or programmatic) automatically writes results to:

.doh/manifests/security_audit.json

This file is written on Node.js only (guarded by IsNode()). It follows the same format as the API response and can be read by any tool without triggering an audit. The file is overwritten on each run.

Example use cases:

  • AI coding agents read the manifest to proactively fix vulnerabilities
  • CI/CD pipelines parse the JSON for gate decisions
  • Monitoring dashboards poll the file for score changes
  • Other Doh CLI commands check for critical findings before proceeding

Schema:

{
  "score": 85,
  "grade": "B",
  "totalChecks": 26,
  "passed": 20,
  "warned": 4,
  "failed": 2,
  "skipped": 0,
  "timestamp": 1707580800000,
  "categories": {
    "auth": { "name": "auth", "passed": 8, "warned": 1, "failed": 1, "skipped": 0, "total": 10 }
  },
  "checks": [
    {
      "id": "permissions.admin_routes_protected",
      "category": "permissions",
      "title": "Admin Routes Protection",
      "description": "Checks that admin routes require authentication and permissions",
      "severity": "critical",
      "hasAutofix": false,
      "status": "fail",
      "message": "2 admin route(s) may lack authentication",
      "details": [
        { "file": "doh_modules/my_app/my_app_server.doh.js", "line": 14, "message": "AddRoute('/admin/settings', ..." },
        { "file": "doh_modules/my_app/my_app_server.doh.js", "line": 28, "message": "AddRoute('/api/admin/config', ..." }
      ],
      "remediation": "Add [Users.requireAuth] middleware to all admin routes",
      "duration": 45
    },
    {
      "id": "server.helmet_enabled",
      "category": "server",
      "title": "Helmet Security Headers",
      "description": "Checks that Helmet is enabled for security headers",
      "severity": "high",
      "hasAutofix": false,
      "status": "pass",
      "message": "Helmet is enabled",
      "details": [],
      "duration": 1
    }
  ]
}

Granting Access

Assign the security_audit permission group to users who should access the audit system:

await Doh.assignPermissionGroup(user, 'security_audit');

This grants both access:security_audit (view results and logs) and manage:security_audit (apply autofixes).

Security Checks

Authentication & Tokens (10 checks)

Check Severity Description
auth.jwt_secret_strength Critical JWT secret length (64+ chars) and entropy (3.5+)
auth.jwt_secrets_differ Critical JWT and refresh secrets are different values
auth.token_expiration Medium Access token <=7d, refresh token <=90d
auth.secure_cookie_flags High Cookies set secure, httpOnly, sameSite
auth.password_policy Medium Min length >=8, requires letter + number + special
auth.bcrypt_rounds Medium Bcrypt rounds >=10
auth.login_rate_limit High Max login attempts <=10, lockout >=60s
auth.password_reset_rate_limit High Password reset routes have rate limiting
auth.oauth_xss_intermediate Critical No unescaped template literals in OAuth redirect scripts
auth.oauth_token_storage High Tokens not written to localStorage via inline script

Server Configuration (7 checks)

Check Severity Description
server.helmet_enabled High Helmet middleware enabled
server.csp_configured High Content Security Policy not disabled
server.cors_not_wildcard High CORS hosts configured, no wildcard *
server.forbidden_paths High Sensitive paths (.env, .git, pod.yaml) blocked
server.body_size_limit Medium Request body limit configured and <=50MB
server.rate_limit_enabled High Global rate limiting configured
server.debug_mode Medium Debug flags (auth_debug, debug_mode) disabled

Database Security (3 checks)

Check Severity Description
database.parameterized_queries High No SQL injection via template literals or string concat
database.db_file_permissions Medium Database files not world-readable (Unix only)
database.idea_table_id_sanitization Medium Client input sanitized before use as Idea table IDs

Input Validation (4 checks)

Check Severity Description
input.username_sanitization High Username sanitizer includes SanitizeEmail
input.password_sanitization High Password sanitizer includes SanitizePassword
input.xss_in_templates High No unescaped user data in res.send, SendContent, or inline scripts
input.eval_usage Critical No eval() or new Function() in application code

Permission System (2 checks)

Check Severity Description
permissions.admin_routes_protected Critical Admin routes require requireAuth or permit()
permissions.require_auth_without_permit Critical Authenticated routes also enforce authorization via Doh.permit()

Scoring

Each check has a severity-based weight:

Severity Weight
Critical 10
High 5
Medium 3
Low 1
Info 0

Check results map to scores: pass = full weight, warn = 50% weight, fail = 0, skip = excluded from scoring.

Grade Score
A 90-100
B 80-89
C 70-79
D 60-69
F < 60

Extending with Custom Checks

Any Doh module can register additional security checks by depending on security_audit_engine:

Doh.Module('my_custom_checks', ['security_audit_engine'], function(SecurityAudit) {

  SecurityAudit.registerCheck('myapp.api_key_rotation', {
    category: 'auth',
    title: 'API Key Rotation',
    description: 'Checks that API keys are rotated within 90 days',
    severity: 'high',
    async run() {
      const keyAge = getApiKeyAgeDays();
      if (keyAge > 90) {
        return {
          status: 'fail',
          message: `API key is ${keyAge} days old (max 90)`,
          remediation: 'Rotate API keys via the admin dashboard'
        };
      }
      if (keyAge > 60) {
        return {
          status: 'warn',
          message: `API key is ${keyAge} days old (rotation recommended at 60)`
        };
      }
      return { status: 'pass', message: `API key is ${keyAge} days old` };
    },
    // Optional: provide an automated fix
    async autofix() {
      await rotateApiKey();
      return 'API key rotated successfully';
    }
  });
});

Check definition fields:

Field Required Description
category Yes Category name (e.g., 'auth', 'server', or a custom one)
title Yes Human-readable title for display
description Yes What the check inspects
severity Yes 'critical' | 'high' | 'medium' | 'low' | 'info'
run Yes Async function returning { status, message, details?, remediation? }
autofix No Async function to auto-fix the issue

Run return values:

Field Required Description
status Yes 'pass' | 'warn' | 'fail' | 'skip'
message Yes Human-readable result message
details No Array of finding objects (normalized to [] if omitted). Each object has message (string) and optionally file (string), line (number), and check-specific fields.
remediation No How to fix the issue

API Routes

Route Method Permission Description
/admin/security GET access:security_audit Dashboard page
/api/admin/security/run POST access:security_audit Execute audit checks
/api/admin/security/results GET access:security_audit Get cached results
/api/admin/security/fix POST manage:security_audit Apply autofix for a check
/api/admin/security/log GET access:security_audit Get paginated log entries
/api/admin/security/log/stats GET access:security_audit Get log statistics

Run Audit

const resp = await Doh.ajaxPromise('/api/admin/security/run', {});
// resp.data.results — full results object
// resp.data.results.score — numeric score (0-100)
// resp.data.results.grade — letter grade (A-F)

// Filter by category
const resp = await Doh.ajaxPromise('/api/admin/security/run', { category: 'auth' });

Apply Autofix

const resp = await Doh.ajaxPromise('/api/admin/security/fix', { checkId: 'auth.jwt_secret_strength' });
// resp.data.success — boolean
// resp.data.message — result message

Get Audit Log

const resp = await Doh.ajaxPromise('/api/admin/security/log?limit=50&offset=0');
// resp.data.entries — array of log entries

const stats = await Doh.ajaxPromise('/api/admin/security/log/stats');
// stats.data.stats.total — total entries
// stats.data.stats.byType — { audit_run: 5, audit_fix: 2, ... }

Audit Logger

Security events are persisted to a Dataforge Idea table (security.audit_log) with 90-day retention (configurable).

Event Types

Event Source Description
audit_run Server / CLI Audit was executed
audit_fix Server Autofix was applied

Logger API (Server-Side)

const { SecurityAuditLogger } = await Doh.load('security_audit_logger');

// Log an event
await SecurityAuditLogger.log('audit_run', 'username', { score: 85, grade: 'B' });

// Get recent entries (newest first)
const entries = await SecurityAuditLogger.getRecent(50, 0);

// Get aggregate stats by event type
const stats = await SecurityAuditLogger.getStats();

Configuration

# boot.pod.yaml or pod.yaml
security_audit:
  enabled: true              # Enable/disable the module (default: true)
  log_retention_days: 90     # How long to keep audit log entries (default: 90)

The module also inspects these pod settings during checks:

  • express_config — helmet, CSP, CORS, rate limiting, body limit, ignored paths
  • Users — JWT secrets, token expiration, password policy, login rate limiting, sanitizers

Module Architecture

security_audit/
  security_audit.doh.js           # Package definition, pod config, CLI registration
  security_audit_engine.doh.js    # Check registry, runner, scoring, manifest output
  security_audit_checks.doh.js    # All 26 built-in checks (AST analysis, file scanning, pod inspection)
  security_audit_logger.doh.js    # Persistent audit event logging via Dataforge
  security_audit_server.doh.js    # Express routes and permission definitions
  security_audit_browser.doh.js   # Browser auto-mount on /admin/security
  security_audit_ui.doh.js        # Dashboard patterns (score circle, category cards, check rows)
  security_audit.css              # Dashboard styles
  dohball.json                    # Package version

Environment loading:

Module Browser Node
security_audit_engine Yes Yes
security_audit_checks - Yes
security_audit_logger - Yes
security_audit_server - Yes
security_audit_browser Yes -
security_audit_ui Yes -

Permissions

  • Context: security_audit
  • Permission Group: security_audit (assignable)
    • access:security_audit — View audit results and logs
    • manage:security_audit — Apply autofixes

Testing

The module includes a server-side test suite at tests/security_audit/:

# Run via the Doh test runner
doh test test_security_audit

Tests cover:

  • Check registration and validation
  • Full audit execution and result shape
  • Category filtering
  • Result caching
  • Logger write, read, and stats

Dependencies

  • express_router — Route handling
  • user_host — Authentication middleware
  • dataforge — Audit log persistence
  • acorn / acorn-walk — AST analysis for code-level checks (Node.js only)
Last updated: 2/17/2026