Skip to content
Back to skills

Security Audit

BSecurity

Comprehensive codebase security assessment producing quantitative scores

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 5, 2026
developmentpythonrustgojavaphpshellbashsqlnextjsnode

Works with

  • claude code
  • cursor
  • vscode
  • terminal
  • cli
  • api

Security analysis

B84/100
  • criticalSends environment variables or credentials to an external URL
  • mediumInstalls packages at runtime which could introduce malicious dependencies

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

Scanned November 5, 2026

npx -y skills add nightgauge/nightgauge --skill security-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Security Audit?

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

Security grade badge for Security Audit
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/nightgauge-security-audit/badge)](https://www.skillsdirectory.com/skills/nightgauge-security-audit)

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
---
name: security-audit
description: Comprehensive codebase security assessment producing quantitative scores
  across 7 dimensions. Use when auditing a codebase for vulnerabilities,
  hardcoded secrets, OWASP Top 10 risks, weak cryptography, input validation
  gaps, authentication flaws, and misconfiguration.
license: Apache-2.0
metadata:
  author: nightgauge
  version: "1.2.0"
  source: https://github.com/nightgauge/nightgauge
  chainable: true
allowed-tools: Read Write Edit Glob Grep Bash Task
orchestration:
  mode: fanout
  phase: security-dimensions
  ceiling: fanout
  units:
    - id: dependency-vulnerabilities
      role: dimension-worker
      promptRef: SKILL.md#phase-1-dependency-vulnerability-scan
    - id: secret-detection
      role: dimension-worker
      promptRef: SKILL.md#phase-2-secret-detection
    - id: owasp-top10
      role: dimension-worker
      promptRef: SKILL.md#phase-3-owasp-top-10-pattern-scan
    - id: cryptographic-health
      role: dimension-worker
      promptRef: SKILL.md#phase-4-cryptographic-health-audit
    - id: input-validation
      role: dimension-worker
      promptRef: SKILL.md#phase-5-input-validation-audit
    - id: auth-authz
      role: dimension-worker
      promptRef: SKILL.md#phase-6-authenticationauthorization-audit
    - id: config-security
      role: dimension-worker
      promptRef: SKILL.md#phase-7-configuration-security-audit
  judge:
    mode: per-unit
    quorum: 1
    promptRef: SKILL.md#phase-8-scoring--report
context: fork
agent: code-reviewer
model: haiku
---

# Nightgauge Security Audit

## Description

Comprehensive codebase security assessment that produces quantitative scores
across 7 dimensions. Uses deterministic regex and bash commands for data
collection; AI interprets raw findings into structured results with 0-100
scoring and false-positive reduction.

**Use Cases:**

- Security posture assessment before shipping or open-sourcing a project
- Identifying hardcoded secrets before a breach occurs
- OWASP Top 10 compliance review
- Periodic security audits on a regular cadence
- Pre-penetration-test gap analysis
- Cross-referencing with health-check output for a full codebase picture

**When to Use:**

- Before a public launch or major release
- When onboarding to an unfamiliar codebase with unknown security history
- After a dependency update that may introduce new CVEs
- On a regular cadence (monthly recommended) for security hygiene
- After adding new authentication or input-handling features

**Relationship to Other Skills:**

| Skill          | Focus                              | Scope        |
| -------------- | ---------------------------------- | ------------ |
| Security Audit | Security vulnerabilities & posture | Any codebase |
| Health Check   | Codebase quality & debt            | Any codebase |
| Pipeline Audit | Pipeline execution efficiency      | Nightgauge   |

## Invocation

| Tool        | Command                                  |
| ----------- | ---------------------------------------- |
| Claude Code | `/nightgauge:security-audit [options]`   |
| Copilot     | Invoke via Agent Skills extension        |
| Cursor      | Run via Agent Skills or direct SKILL.md  |
| Headless    | `claude -p "/nightgauge:security-audit"` |

## Arguments

| Argument            | Description                                                             | Default |
| ------------------- | ----------------------------------------------------------------------- | ------- |
| `--path DIR`        | Root directory to assess                                                | `.`     |
| `--package PKG`     | Assess specific monorepo package only                                   | -       |
| `--dimensions DIMS` | Comma-separated dimensions to analyze                                   | `all`   |
| `--format FORMAT`   | Output format: `summary`, `json`, `both`                                | `both`  |
| `--skip-audit`      | Skip dependency audit commands                                          | `false` |
| `--output FILE`     | Custom output path for JSON report                                      | auto    |
| `--severity LEVEL`  | Minimum severity to report: `info`, `low`, `medium`, `high`, `critical` | `low`   |

### Examples

```bash
# Full security assessment of current directory
/nightgauge:security-audit

# Assess specific directory
/nightgauge:security-audit --path /path/to/project

# Only analyze secrets and OWASP risks
/nightgauge:security-audit --dimensions secret-detection,owasp-top10

# JSON output only, skip dependency audit
/nightgauge:security-audit --format json --skip-audit

# Only report high and critical findings
/nightgauge:security-audit --severity high

# Assess specific monorepo package
/nightgauge:security-audit --package packages/api-server
```

---

## Prerequisites

- Bash shell
- `jq` installed (for JSON processing)
- Ecosystem audit tools are optional — the skill gracefully degrades when audit
  tools (`npm audit`, `pip-audit`, `cargo audit`, `govulncheck`) are not
  installed

**CRITICAL**: This skill runs headless. Do NOT use AskUserQuestion. Make
autonomous decisions or fail with a clear error message.

---

## Security Dimensions

### 1. Dependency Vulnerabilities (Weight: 0.20)

Evaluates known CVEs in third-party dependencies using ecosystem-specific audit
tooling.

**Metrics:**

- CVE count by severity (critical/high/medium/low)
- Total dependency count
- Audit tool used and its availability
- Whether `--skip-audit` was requested

**Scoring:**

| Score Range | Condition                                              |
| ----------- | ------------------------------------------------------ |
| 90-100      | No vulnerabilities found by audit tool                 |
| 70-89       | Only low/info vulnerabilities, no critical or high     |
| 50-69       | 1-2 medium vulnerabilities, no critical or high        |
| 30-49       | 1-2 high vulnerabilities or multiple mediums           |
| 0-29        | Any critical vulnerability, or 3+ high vulnerabilities |

### 2. Secret Detection (Weight: 0.20)

Scans source files for hardcoded credentials, API keys, tokens, and private keys
using deterministic regex patterns. Excludes test fixtures and example files
where secrets are expected to be fake.

**Metrics:**

- Count of pattern matches by category (api key, password, token, private key)
- Files with `.env` patterns tracked but not committed (healthy signal)
- `.gitignore` presence and coverage of sensitive file types
- Secret-like strings in configuration files

**Scoring:**

| Score Range | Condition                                                 |
| ----------- | --------------------------------------------------------- |
| 90-100      | No secret patterns found; .gitignore covers .env and keys |
| 70-89       | 1-2 low-confidence matches, likely false positives        |
| 50-69       | 3-5 pattern matches requiring manual review               |
| 30-49       | 6-10 pattern matches, some in production code paths       |
| 0-29        | Any confirmed hardcoded secret in production code         |

### 3. OWASP Top 10 (Weight: 0.15)

Scans for common OWASP Top 10 vulnerability patterns using regex. AI interprets
matches to reduce false positives based on surrounding context.

**Categories assessed:**

- A01 Broken Access Control
- A02 Cryptographic Failures (see also Dimension 4)
- A03 Injection (SQL, command, LDAP, XPath)
- A04 Insecure Design (SSRF, path traversal)
- A05 Security Misconfiguration (debug mode, default creds)
- A07 Identification/Authentication Failures
- A08 Software/Data Integrity Failures (unsafe deserialization)
- A10 Server-Side Request Forgery

**Scoring:**

| Score Range | Condition                                               |
| ----------- | ------------------------------------------------------- |
| 90-100      | No OWASP patterns detected                              |
| 70-89       | 1-3 low-risk patterns, likely false positives           |
| 50-69       | 4-8 pattern matches, context suggests possible risk     |
| 30-49       | Multiple injection or traversal patterns in active code |
| 0-29        | Confirmed injection or SSRF patterns in production code |

### 4. Cryptographic Health (Weight: 0.10)

Identifies use of deprecated or weak cryptographic algorithms, insecure modes,
and insufficient key sizes.

**Metrics:**

- MD5, SHA1, DES, RC4, ECB mode usage
- Hardcoded initialization vectors (IV) or static salts
- HTTP (non-TLS) URLs in production configuration
- Certificate pinning absence in mobile/native apps

**Scoring:**

| Score Range | Condition                                               |
| ----------- | ------------------------------------------------------- |
| 90-100      | No weak crypto patterns; modern algorithms used         |
| 70-89       | 1-2 deprecated algo calls, likely in non-security paths |
| 50-69       | 3-5 deprecated algo calls requiring review              |
| 30-49       | Weak crypto in security-relevant paths                  |
| 0-29        | MD5/SHA1 used for password hashing or signing           |

### 5. Input Validation (Weight: 0.15)

Detects missing sanitization, unparameterized queries, raw user input passed to
templating engines or system calls, and unsafe deserialization.

**Metrics:**

- String-concatenated SQL query patterns
- Raw `req.body`, `req.query`, `req.params` usage without validation middleware
- Template injection patterns (user input in template strings)
- `eval()` or `Function()` called with external input
- Unsafe YAML/JSON deserialization

**Scoring:**

| Score Range | Condition                                                          |
| ----------- | ------------------------------------------------------------------ |
| 90-100      | Parameterized queries used throughout; validation middleware found |
| 70-89       | 1-2 unvalidated input patterns, low-risk context                   |
| 50-69       | 3-5 patterns; input reaches DB or template without sanitization    |
| 30-49       | Multiple unparameterized queries or eval with external input       |
| 0-29        | Confirmed SQL injection or template injection vectors              |

### 6. Authentication/Authorization (Weight: 0.10)

Detects broken access control, missing authentication middleware, hardcoded
credentials, JWT misconfigurations, and session management issues.

**Metrics:**

- Route handlers without auth middleware
- Hardcoded usernames, passwords, or JWT secrets in code
- JWT verification skipped or `alg: none` usage
- Missing authorization checks on resource access endpoints
- Session tokens with insecure attributes (`httpOnly: false`, `secure: false`)

**Scoring:**

| Score Range | Condition                                               |
| ----------- | ------------------------------------------------------- |
| 90-100      | Auth middleware present; no hardcoded credentials found |
| 70-89       | 1-2 routes missing auth, low-risk endpoints             |
| 50-69       | Multiple unprotected routes or weak session config      |
| 30-49       | Hardcoded credentials or JWT alg bypass patterns        |
| 0-29        | Confirmed broken access control on sensitive endpoints  |

### 7. Configuration Security (Weight: 0.10)

Checks for insecure application configuration: debug mode in production, exposed
admin endpoints, permissive CORS, missing HTTP security headers, and default or
example credentials left in config files.

**Metrics:**

- `DEBUG=true` or `NODE_ENV=development` in committed config
- CORS `*` wildcard in production configuration
- Missing `helmet` or equivalent security header middleware
- Exposed `/admin`, `/debug`, `/metrics` without auth protection
- Default passwords in config files (`admin`, `password`, `123456`)

**Scoring:**

| Score Range | Condition                                                   |
| ----------- | ----------------------------------------------------------- |
| 90-100      | Security headers present; no debug mode; CORS restricted    |
| 70-89       | 1-2 minor misconfigurations (missing headers, etc.)         |
| 50-69       | CORS wildcard or debug mode committed to repo               |
| 30-49       | Multiple misconfigurations; exposed admin endpoints         |
| 0-29        | Debug mode + permissive CORS + default credentials combined |

---

## Orchestration

This skill declares an `orchestration:` frontmatter block (`mode: fanout`) that
fans the seven security dimensions out as parallel worker units, one per
dimension, each adversarially checked by a per-unit judge before its score is
accepted. The block is consumed by the capability-routed `WorkflowEngine` (see
[docs/WORKFLOW_ORCHESTRATION.md](https://github.com/nightgauge/nightgauge/blob/main/docs/WORKFLOW_ORCHESTRATION.md)) on
providers that declare an orchestration capability.

The prose **Workflow** below is the **single-agent portability floor**: each
unit's `promptRef` points at the SAME dimension phase a single agent walks
sequentially, so providers without orchestration (Copilot, Cursor) run the exact
same assessment one phase at a time. Orchestration is an acceleration of the
prose, never a divergence from it.

## Workflow

### Phase 0: Environment Detection

### Phase 0: Environment Preflight

**PURPOSE**: Verify the nightgauge binary is available and the environment
is healthy before executing any pipeline stage. Skills halt immediately with
actionable instructions when this check fails.

Three-layer check:

1. **Binary discovery** — shell-level, runs before invoking the binary
2. **Doctor check** — binary-level, full environment health via `doctor --json`
3. **Project environment** — the checks the project declares under
   `preflight.environment` (#101); opt-in, silent when none are declared

The binary discovery cascade below is **provider-neutral** (issue 4029): it works
identically under every adapter (Claude, Codex, Gemini, …) and references no
VSCode-extension-specific path. The host that spawns the skill (the VSCode
extension's `skillRunner`, or the Go `nightgauge` binary in auto/CLI mode)
resolves the binary authoritatively and exports `NIGHTGAUGE_BIN`, which
this cascade honors first. The remaining steps (PATH, the development-only repo
`bin/` and worktree canonical-repo `bin/`, `~/go/bin`) are fallbacks for direct
or terminal invocation.

This intentionally **diverges** from
`claude-plugins/nightgauge/hooks/lib/guard.sh` (issue 3262 → issue 4029): guard.sh is
Claude-Code-only and retains a trailing `~/.vscode/extensions/...` glob to serve
the standalone-terminal-Claude case (where no host exports `NIGHTGAUGE_BIN`).
Skills must stay portable Markdown and cannot reference that VSCode-only path —
so the shared resolution order (`NIGHTGAUGE_BIN` → PATH → gated repo → gated
canonical → `~/go/bin`) stays in sync, and only guard.sh carries the extra Claude-only
fallback. The `nightgauge preflight skill-portability` gate enforces that
no skill reintroduces a `.vscode/extensions` path, and that every cascade keeps
the `NIGHTGAUGE_DEV_REPO_BIN` gate (#2758): a foreign repository's
`bin/nightgauge` must never run.

After the cascade resolves `$BINARY`, the preflight prepends
`dirname($BINARY)` to `PATH` so subsequent bare `nightgauge ...`
invocations later in the same skill body resolve through PATH rather than
failing with "command not found" (issue 3262).

```bash
# Layer 1: provider-neutral binary discovery (NIGHTGAUGE_BIN → PATH → gated repo
# bin → gated canonical-repo bin → ~/go/bin). No VSCode-extension path — see issue 4029.
# Repo bins need NIGHTGAUGE_DEV_REPO_BIN=1 and a verified core checkout (#2758).
BINARY="${NIGHTGAUGE_BIN:-}"
[ -n "$BINARY" ] && [ ! -x "$BINARY" ] && BINARY=""
[ -z "$BINARY" ] && BINARY=$(command -v nightgauge 2>/dev/null || echo "")
if [ -z "$BINARY" ] && [ "${NIGHTGAUGE_DEV_REPO_BIN:-}" = "1" ]; then
  for NG_ROOT in "$(git rev-parse --show-toplevel 2>/dev/null)" \
    "$(cd "$(git rev-parse --git-common-dir 2>/dev/null)/.." 2>/dev/null && pwd)"; do
    [ -x "$NG_ROOT/bin/nightgauge" ] || continue
    grep -qx 'module github.com/nightgauge/nightgauge' "$NG_ROOT/go.mod" 2>/dev/null || continue
    NG_ORIGIN="$(git -C "$NG_ROOT" config --get remote.origin.url 2>/dev/null)"
    case "${NG_ORIGIN%.git}" in
      git@github.com:nightgauge/nightgauge | https://github.com/nightgauge/nightgauge | \
        ssh://git@github.com/nightgauge/nightgauge) BINARY="$NG_ROOT/bin/nightgauge"; break ;;
    esac
  done
fi
[ -z "$BINARY" ] && [ -x "$HOME/go/bin/nightgauge" ] && BINARY="$HOME/go/bin/nightgauge"
if [ -z "$BINARY" ]; then
  echo "ERROR: nightgauge binary not found via NIGHTGAUGE_BIN, PATH or ~/go/bin" >&2
  echo "" >&2
  echo "Install via: go install github.com/nightgauge/nightgauge/cmd/nightgauge@latest" >&2
  echo "Or download from: https://github.com/nightgauge/nightgauge/releases" >&2
  echo "Run \`nightgauge doctor\` after installing to verify your environment." >&2
  exit 1
fi
# Make the resolved binary callable via bare `nightgauge ...` later in this skill body (issue 3262).
export PATH="$(dirname "$BINARY"):$PATH"

# Per-repo GitHub token: export GH_TOKEN so every `gh` call in this skill
# authenticates as the *current repo's* configured user instead of the machine-
# global `gh auth` active account. `forge auth token` resolves per-repo (config
# github_user/token → GITHUB_TOKEN → `gh auth token --user <github_user>`), so
# concurrent sessions/workspaces owned by different GitHub users each use their
# own token. GH_TOKEN outranks GITHUB_TOKEN and the gh keyring. Only resolve when
# unset (an upstream env value — e.g. the VSCode extension's terminal env —
# wins); never fail preflight over it. Mirrors hooks/lib/guard.sh (sync, issue 3262).
if [ -z "${GH_TOKEN:-}" ]; then
  IB_REPO_TOKEN="$("$BINARY" forge auth token 2>/dev/null || true)"
  if [ -n "$IB_REPO_TOKEN" ]; then
    export GH_TOKEN="$IB_REPO_TOKEN"
    export GITHUB_TOKEN="${GITHUB_TOKEN:-$IB_REPO_TOKEN}"
  fi
  unset IB_REPO_TOKEN
fi

# Layer 2: full environment health check via doctor --json
DOCTOR_RESULT=$("$BINARY" doctor --json 2>/dev/null)
DOCTOR_EXIT=$?
if [ "$DOCTOR_EXIT" -eq 2 ]; then
  echo "ERROR: Environment check failed — nightgauge doctor reports broken environment." >&2
  FAILED_CHECKS=$(printf '%s\n' "$DOCTOR_RESULT" | jq -r '.failed_checks[]? // empty' 2>/dev/null)
  if [ -n "$FAILED_CHECKS" ]; then
    echo "Failing check(s): $(echo "$FAILED_CHECKS" | tr '\n' ',' | sed 's/,$//')" >&2
  fi
  printf '%s\n' "$DOCTOR_RESULT" | jq -r '.errors[]' >&2 2>/dev/null || true
  INSTALL_MSG=$(printf '%s\n' "$DOCTOR_RESULT" | jq -r '.install_instructions // empty' 2>/dev/null)
  [ -n "$INSTALL_MSG" ] && echo "$INSTALL_MSG" >&2
  exit 1
fi
if [ "$DOCTOR_EXIT" -eq 1 ]; then
  echo "WARNING: Environment has non-critical issues:" >&2
  printf '%s\n' "$DOCTOR_RESULT" | jq -r '.warnings[]' >&2 2>/dev/null || true
  # Continue — warnings do not block skill execution
fi

# Layer 3: project-declared environment checks (preflight.environment, #101).
# Opt-in: with none declared this prints nothing and continues. The verb
# writes one `[environment-halt]` line per failed check, with its remedy.
if ! "$BINARY" preflight environment >/dev/null; then
  echo "[environment-halt] Project environment check failed — fix the environment, not the code." >&2
  exit 1
fi
```

**Exit codes from `nightgauge doctor`**:

| Code | Meaning                         | Skill behavior                          |
| ---- | ------------------------------- | --------------------------------------- |
| 0    | Healthy                         | Continue                                |
| 1    | Degraded (warnings only)        | Continue with warning printed to stderr |
| 2    | Broken (required checks failed) | Halt immediately with error details     |

**Layer 3** halts the stage when any check declared under
`preflight.environment` in the workspace's `.nightgauge/config.yaml` fails
(exit 1) or the declaration is invalid (exit 2). The failure is in the
operator's environment, not the code: do not edit code to work around it, do
not run the remedy yourself, and end the stage. The pipeline records it as
`environment_halt` and holds the issue for an operator.

Do not theorize about the cause beyond what is printed here — the failing
check name (`failed_checks`) and its `.errors[]` message are the complete
diagnosis (#277).


---

#### Step 0.1: Parse Arguments

Extract options from invocation:

```bash
ASSESS_PATH="."
PACKAGE_FILTER=""
DIMENSIONS="all"
OUTPUT_FORMAT="both"
SKIP_AUDIT=false
OUTPUT_FILE=""
SEVERITY_FILTER="low"

# Parse arguments from invocation
# --path DIR: set ASSESS_PATH
# --package PKG: set PACKAGE_FILTER
# --dimensions DIMS: set DIMENSIONS (comma-separated)
# --format FORMAT: set OUTPUT_FORMAT
# --skip-audit: set SKIP_AUDIT=true
# --output FILE: set OUTPUT_FILE
# --severity LEVEL: set SEVERITY_FILTER
```

#### Step 0.2: Detect Ecosystems

Scan for ecosystem indicators in the assessment path:

```bash
cd "$ASSESS_PATH"
ECOSYSTEMS=()

# Node.js
[ -f package.json ] && ECOSYSTEMS+=("nodejs")

# Python
([ -f pyproject.toml ] || [ -f setup.py ] || \
 [ -f requirements.txt ]) && ECOSYSTEMS+=("python")

# Go
[ -f go.mod ] && ECOSYSTEMS+=("go")

# Rust
[ -f Cargo.toml ] && ECOSYSTEMS+=("rust")

# Java/JVM
([ -f pom.xml ] || [ -f build.gradle ] || \
 [ -f build.gradle.kts ]) && ECOSYSTEMS+=("java")

if [ ${#ECOSYSTEMS[@]} -eq 0 ]; then
  echo "WARNING: No recognized ecosystem detected."
  echo "  Checked: package.json, pyproject.toml, setup.py,"
  echo "  requirements.txt, go.mod, Cargo.toml, pom.xml,"
  echo "  build.gradle, build.gradle.kts"
  echo "  Proceeding with generic file analysis only."
fi

echo "Ecosystems detected: ${ECOSYSTEMS[*]}"
```

#### Step 0.3: Detect Monorepo Structure

Check for workspace/monorepo indicators:

```bash
IS_MONOREPO=false
PACKAGES=()

# Node.js workspaces
if [ -f package.json ]; then
  WORKSPACES=$(jq -r '.workspaces // empty' package.json 2>/dev/null)
  if [ -n "$WORKSPACES" ]; then
    IS_MONOREPO=true
    # Expand workspace globs to actual directories
    for ws in $(jq -r '.workspaces[]? // empty' package.json 2>/dev/null); do
      for dir in $ws; do
        [ -d "$dir" ] && PACKAGES+=("$dir")
      done
    done
  fi
fi

# Cargo workspace
if [ -f Cargo.toml ]; then
  if grep -q '\[workspace\]' Cargo.toml 2>/dev/null; then
    IS_MONOREPO=true
  fi
fi

# Go workspace
if [ -f go.work ]; then
  IS_MONOREPO=true
fi

echo "Monorepo: $IS_MONOREPO"
if [ "$IS_MONOREPO" = true ]; then
  echo "Packages: ${PACKAGES[*]}"
fi
```

#### Step 0.4: Detect .gitignore and Exclusion Patterns

Read `.gitignore` to understand which sensitive file types are already excluded,
and build the file-scan exclusion list:

```bash
GITIGNORE_COVERS_ENV=false
GITIGNORE_COVERS_KEYS=false

if [ -f .gitignore ]; then
  grep -qE '^\s*\.env' .gitignore 2>/dev/null && GITIGNORE_COVERS_ENV=true
  grep -qE '^\s*\*\.(pem|key|p12|pfx|crt)' .gitignore 2>/dev/null && \
    GITIGNORE_COVERS_KEYS=true
fi

echo ".gitignore covers .env files: $GITIGNORE_COVERS_ENV"
echo ".gitignore covers key files: $GITIGNORE_COVERS_KEYS"

# Standard exclusion dirs used in all subsequent scans
EXCLUDE_DIRS=(
  "node_modules" ".git" "vendor" "dist" "build"
  ".nightgauge" "coverage" ".nyc_output"
)
```

#### Step 0.5: Check for Health Check Integration

Detect existing health-check output to cross-reference:

```bash
HEALTH_CHECK_PATH="$(nightgauge layout path checkout reports/health-report.json)"
HEALTH_CHECK_AVAILABLE=false

if [ -f "$HEALTH_CHECK_PATH" ]; then
  HEALTH_CHECK_AVAILABLE=true
  echo "Health check report found: $HEALTH_CHECK_PATH"
  # Extract dependency health score from health-check for context
  HC_DEP_SCORE=$(jq -r \
    '.dimensions.dependency_health.score // "N/A"' \
    "$HEALTH_CHECK_PATH" 2>/dev/null || echo "N/A")
  echo "  Health check dependency score: $HC_DEP_SCORE"
else
  echo "No health check report found. Run /nightgauge:health-check first"
  echo "  for cross-referenced dependency data."
fi
```

---

### Phase 1: Dependency Vulnerability Scan

Only runs if `DIMENSIONS` includes `dependency-vulnerabilities` or is `all`.

#### Step 1.1: Run Ecosystem Audit Tools

Run ecosystem-specific audit tools. Gracefully skip if the tool is not installed
or `--skip-audit` is set.

**Node.js:**

```bash
if [[ " ${ECOSYSTEMS[*]} " =~ " nodejs " ]]; then
  if command -v npm &>/dev/null && [ "$SKIP_AUDIT" != true ]; then
    echo "Running npm audit..."
    npm audit --json 2>/dev/null > /tmp/sa_npm_audit.json || true
    # Parse vulnerability counts by severity
    CRIT_COUNT=$(jq -r \
      '.metadata.vulnerabilities.critical // 0' \
      /tmp/sa_npm_audit.json 2>/dev/null || echo 0)
    HIGH_COUNT=$(jq -r \
      '.metadata.vulnerabilities.high // 0' \
      /tmp/sa_npm_audit.json 2>/dev/null || echo 0)
    MED_COUNT=$(jq -r \
      '.metadata.vulnerabilities.moderate // 0' \
      /tmp/sa_npm_audit.json 2>/dev/null || echo 0)
    LOW_COUNT=$(jq -r \
      '.metadata.vulnerabilities.low // 0' \
      /tmp/sa_npm_audit.json 2>/dev/null || echo 0)
    echo "npm audit: critical=$CRIT_COUNT high=$HIGH_COUNT" \
         "medium=$MED_COUNT low=$LOW_COUNT"
  else
    echo "npm audit skipped (tool unavailable or --skip-audit)"
  fi
  DEP_COUNT=$(jq -r \
    '(.dependencies // {} | length) + (.devDependencies // {} | length)' \
    package.json 2>/dev/null || echo 0)
  echo "Total npm dependencies: $DEP_COUNT"
fi
```

**Python:**

```bash
if [[ " ${ECOSYSTEMS[*]} " =~ " python " ]]; then
  if command -v pip-audit &>/dev/null && [ "$SKIP_AUDIT" != true ]; then
    echo "Running pip-audit..."
    pip-audit --format json 2>/dev/null > /tmp/sa_pip_audit.json || true
    VULN_COUNT=$(jq -r '[.[] | .vulns[]] | length' \
      /tmp/sa_pip_audit.json 2>/dev/null || echo 0)
    echo "pip-audit: $VULN_COUNT vulnerabilities found"
  else
    echo "pip-audit skipped (tool unavailable or --skip-audit)"
  fi
fi
```

**Go:**

```bash
if [[ " ${ECOSYSTEMS[*]} " =~ " go " ]]; then
  if command -v govulncheck &>/dev/null && [ "$SKIP_AUDIT" != true ]; then
    echo "Running govulncheck..."
    govulncheck -json ./... 2>/dev/null > /tmp/sa_go_vuln.json || true
    VULN_COUNT=$(jq -r \
      '[.[] | select(.finding)] | length' \
      /tmp/sa_go_vuln.json 2>/dev/null || echo 0)
    echo "govulncheck: $VULN_COUNT findings"
  else
    echo "govulncheck skipped (tool unavailable or --skip-audit)"
  fi
fi
```

**Rust:**

```bash
if [[ " ${ECOSYSTEMS[*]} " =~ " rust " ]]; then
  if command -v cargo-audit &>/dev/null && [ "$SKIP_AUDIT" != true ]; then
    echo "Running cargo audit..."
    cargo audit --json 2>/dev/null > /tmp/sa_cargo_audit.json || true
    VULN_COUNT=$(jq -r \
      '.vulnerabilities.count // 0' \
      /tmp/sa_cargo_audit.json 2>/dev/null || echo 0)
    echo "cargo audit: $VULN_COUNT vulnerabilities"
  else
    echo "cargo audit skipped (tool unavailable or --skip-audit)"
  fi
fi
```

**Java:**

```bash
if [[ " ${ECOSYSTEMS[*]} " =~ " java " ]]; then
  # OWASP Dependency-Check (if installed as CLI tool)
  if command -v dependency-check &>/dev/null && [ "$SKIP_AUDIT" != true ]; then
    echo "Running OWASP dependency-check..."
    dependency-check --scan . --format JSON --out /tmp/sa_depcheck.json \
      2>/dev/null || true
  else
    echo "dependency-check skipped (tool unavailable or --skip-audit)"
  fi
fi
```

#### Step 1.2: Compute Score

AI interprets the collected vulnerability counts using the scoring rubric for
Dimension 1. Cross-reference with health-check dependency data when available.
Write results to `/tmp/sa_dim_depvuln.json`.

---

### Phase 2: Secret Detection

Only runs if `DIMENSIONS` includes `secret-detection` or is `all`.

#### Step 2.1: Build Exclusion Arguments

```bash
# Build grep exclusion arguments from EXCLUDE_DIRS
GREP_EXCLUDES=""
for d in "${EXCLUDE_DIRS[@]}"; do
  GREP_EXCLUDES="$GREP_EXCLUDES --exclude-dir=$d"
done
```

#### Step 2.2: Scan for Common Secret Patterns

The deterministic six-pattern scan is delegated to the Go binary
(`nightgauge scan secrets`). The verb replicates the original
`grep -rn ... | wc -l` chain exactly — same six patterns, same false-positive
filters, same per-pattern file-extension allowlists, same line-count
semantics. AI reduces false positives based on file type and surrounding
context in the scoring step (Phase 2.4).

See [docs/GO_BINARY.md](https://github.com/nightgauge/nightgauge/blob/main/docs/GO_BINARY.md#scan--secret-pattern-detection)
for the full pattern table and JSON shape (audit row B41).

```bash
echo "=== Secret Detection ==="

SECRETS_JSON=$(nightgauge scan secrets --workdir "$ASSESS_PATH" --json)

# Per-pattern line counts (always populated — schema guarantees the six keys).
SECRET_GENERIC=$(printf '%s\n' "$SECRETS_JSON" | jq -r '.patterns.generic_kv')
SECRET_PEM=$(printf '%s\n' "$SECRETS_JSON" | jq -r '.patterns.pem_private_key')
SECRET_AWS=$(printf '%s\n' "$SECRETS_JSON" | jq -r '.patterns.aws_access_key')
SECRET_JWT=$(printf '%s\n' "$SECRETS_JSON" | jq -r '.patterns.jwt_bearer')
SECRET_CONNSTR=$(printf '%s\n' "$SECRETS_JSON" | jq -r '.patterns.connection_string')
SECRET_DOTENV=$(printf '%s\n' "$SECRETS_JSON" | jq -r '.patterns.dotenv_files')
SECRET_TOTAL=$(printf '%s\n' "$SECRETS_JSON" | jq -r '.total')

echo "--- Generic key/value secrets ---"
echo "Generic key/value patterns: $SECRET_GENERIC"
echo "--- PEM private keys ---"
echo "PEM private key blocks: $SECRET_PEM"
echo "--- AWS access keys ---"
echo "AWS access key patterns: $SECRET_AWS"
echo "--- JWT / bearer tokens ---"
echo "JWT/bearer token patterns: $SECRET_JWT"
echo "--- Embedded connection strings ---"
echo "Embedded connection string patterns: $SECRET_CONNSTR"
echo "--- Committed .env files ---"
echo "Committed .env files: $SECRET_DOTENV"
echo "Total secret pattern matches: $SECRET_TOTAL"
```

#### Step 2.3: Assess .gitignore Coverage

```bash
echo "--- .gitignore assessment ---"
echo ".env covered: $GITIGNORE_COVERS_ENV"
echo "Key files covered: $GITIGNORE_COVERS_KEYS"
```

#### Step 2.4: Compute Score

AI interprets the collected pattern matches, filters false positives based on
file context (test files, example files, `.env.example`), and computes a 0-100
score per the Dimension 2 rubric. Write results to `/tmp/sa_dim_secrets.json`.

---

### Phase 3: OWASP Top 10 Pattern Scan

Only runs if `DIMENSIONS` includes `owasp-top10` or is `all`.

#### Step 3.1: SQL Injection Patterns

```bash
echo "=== OWASP: SQL Injection ==="

# String concatenation in SQL queries (A03)
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.rs" --include="*.java" --include="*.kt" \
  --include="*.php" --include="*.rb" \
  $GREP_EXCLUDES \
  -iE '(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)\s+.*\+\s*\w+|\$\{.*\}.*sql|query\s*\(\s*[`'"'"'"].*\+' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_owasp_sqli.txt || true
SQLI_COUNT=$(wc -l < /tmp/sa_owasp_sqli.txt | tr -d ' ')
echo "SQL injection patterns: $SQLI_COUNT"
```

#### Step 3.2: Command Injection Patterns

```bash
echo "=== OWASP: Command Injection ==="

# exec/spawn/system called with user-controlled input (A03)
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.rs" --include="*.java" \
  $GREP_EXCLUDES \
  -iE '(exec|spawn|system|popen|execSync|spawnSync)\s*\(.*(\$\{|\+\s*\w+|req\.|args\[)' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_owasp_cmdi.txt || true
CMDI_COUNT=$(wc -l < /tmp/sa_owasp_cmdi.txt | tr -d ' ')
echo "Command injection patterns: $CMDI_COUNT"
```

#### Step 3.3: Path Traversal Patterns

```bash
echo "=== OWASP: Path Traversal ==="

# Path traversal via user input (A04)
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.java" \
  $GREP_EXCLUDES \
  -iE '(\.\./|\.\.\\|path\.join\s*\(.*req\.|readFile\s*\(.*req\.)' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_owasp_traversal.txt || true
TRAVERSAL_COUNT=$(wc -l < /tmp/sa_owasp_traversal.txt | tr -d ' ')
echo "Path traversal patterns: $TRAVERSAL_COUNT"
```

#### Step 3.4: SSRF Patterns

```bash
echo "=== OWASP: SSRF ==="

# Server-side request forgery — user-controlled URL passed to fetch/http (A10)
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.java" \
  $GREP_EXCLUDES \
  -iE '(fetch|axios|got|request|http\.get|urllib)\s*\(.*\b(req\.|params\.|query\.|body\.)' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_owasp_ssrf.txt || true
SSRF_COUNT=$(wc -l < /tmp/sa_owasp_ssrf.txt | tr -d ' ')
echo "SSRF patterns: $SSRF_COUNT"
```

#### Step 3.5: XSS Patterns

```bash
echo "=== OWASP: XSS ==="

# Unsafe innerHTML / dangerouslySetInnerHTML / document.write (A03)
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.html" \
  $GREP_EXCLUDES \
  -iE '(innerHTML\s*=|dangerouslySetInnerHTML|document\.write\s*\(|\.html\s*\(.*req\.)' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_owasp_xss.txt || true
XSS_COUNT=$(wc -l < /tmp/sa_owasp_xss.txt | tr -d ' ')
echo "XSS patterns: $XSS_COUNT"
```

#### Step 3.6: Unsafe Deserialization

```bash
echo "=== OWASP: Unsafe Deserialization ==="

# pickle.loads, yaml.load (unsafe), unserialize (A08)
grep -rn --include="*.py" --include="*.js" --include="*.ts" \
  --include="*.php" --include="*.java" \
  $GREP_EXCLUDES \
  -iE '(pickle\.loads|yaml\.load\s*\([^,)]+\)|unserialize\s*\()' \
  "$ASSESS_PATH" 2>/dev/null | \
  grep -vE 'yaml\.safe_load' \
  > /tmp/sa_owasp_deser.txt || true
DESER_COUNT=$(wc -l < /tmp/sa_owasp_deser.txt | tr -d ' ')
echo "Unsafe deserialization patterns: $DESER_COUNT"
```

#### Step 3.7: Compute Score

AI interprets the pattern matches with context awareness to distinguish
confirmed risks from false positives (e.g., parameterized queries that happen to
match the regex). Computes a 0-100 score per the Dimension 3 rubric. Write
results to `/tmp/sa_dim_owasp.json`.

---

### Phase 4: Cryptographic Health Audit

Only runs if `DIMENSIONS` includes `cryptographic-health` or is `all`.

#### Step 4.1: Detect Weak Algorithm Usage

```bash
echo "=== Crypto: Weak Algorithms ==="

# MD5, SHA1, DES, RC4, ECB mode (A02)
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.rs" --include="*.java" --include="*.kt" \
  --include="*.rb" --include="*.php" \
  $GREP_EXCLUDES \
  -iE '(md5|sha1|des|rc4|ecb)\s*\(' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_crypto_weak.txt || true
WEAK_CRYPTO=$(wc -l < /tmp/sa_crypto_weak.txt | tr -d ' ')
echo "Weak algorithm calls: $WEAK_CRYPTO"

# createHash('md5') or createHash('sha1') Node.js
grep -rn --include="*.ts" --include="*.js" \
  $GREP_EXCLUDES \
  -iE "createHash\s*\(\s*['\"]?(md5|sha1)['\"]?\s*\)" \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_crypto_nodehash.txt || true
WEAK_NODEHASH=$(wc -l < /tmp/sa_crypto_nodehash.txt | tr -d ' ')
echo "Node.js weak hash calls: $WEAK_NODEHASH"
```

#### Step 4.2: Detect Hardcoded IVs and Static Salts

```bash
echo "=== Crypto: Hardcoded IV / Static Salts ==="

grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.java" \
  $GREP_EXCLUDES \
  -iE '(iv|salt|nonce)\s*=\s*['"'"'"][0-9a-fA-F]{8,}['"'"'"]|Buffer\.from\s*\(\s*['"'"'"][0-9a-fA-F]{8,}' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_crypto_iv.txt || true
HARDCODED_IV=$(wc -l < /tmp/sa_crypto_iv.txt | tr -d ' ')
echo "Hardcoded IV/salt patterns: $HARDCODED_IV"
```

#### Step 4.3: Detect Non-TLS HTTP in Production Config

```bash
echo "=== Crypto: HTTP (non-TLS) URLs in config ==="

grep -rn --include="*.yaml" --include="*.yml" --include="*.json" \
  --include="*.toml" --include="*.env" --include="*.cfg" \
  --include="*.conf" --include="*.ini" \
  $GREP_EXCLUDES \
  -iE 'http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0)' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_crypto_http.txt || true
HTTP_URLS=$(wc -l < /tmp/sa_crypto_http.txt | tr -d ' ')
echo "Non-TLS HTTP URLs in config: $HTTP_URLS"
```

#### Step 4.4: Compute Score

AI interprets the collected weak-crypto matches. Distinguishes security-relevant
paths (password hashing, signing) from non-security paths (checksums, ETags).
Computes a 0-100 score per the Dimension 4 rubric. Write results to
`/tmp/sa_dim_crypto.json`.

---

### Phase 5: Input Validation Audit

Only runs if `DIMENSIONS` includes `input-validation` or is `all`.

#### Step 5.1: Detect Unparameterized Database Queries

```bash
echo "=== Input Validation: Unparameterized Queries ==="

# Raw string concatenation into SQL (Node.js/Python/Go/Java)
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.java" --include="*.kt" \
  $GREP_EXCLUDES \
  -iE '(query|execute|raw)\s*\(\s*[`'"'"'"]?\s*(SELECT|INSERT|UPDATE|DELETE).*\+|f['"'"'"]\s*(SELECT|INSERT|UPDATE|DELETE)' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_input_rawsql.txt || true
RAW_SQL=$(wc -l < /tmp/sa_input_rawsql.txt | tr -d ' ')
echo "Unparameterized query patterns: $RAW_SQL"
```

#### Step 5.2: Detect Raw User Input Without Validation

```bash
echo "=== Input Validation: Unvalidated User Input ==="

# req.body/req.query/req.params used directly without validation (Express/Koa)
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" \
  $GREP_EXCLUDES \
  -iE '(req\.body|req\.query|req\.params)\.\w+\s*(?!\.trim|\.toString|\.replace|\.match|schema|validate|sanitize|parse)' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_input_raw.txt || true
RAW_INPUT=$(wc -l < /tmp/sa_input_raw.txt | tr -d ' ')
echo "Unvalidated input patterns (raw req.*): $RAW_INPUT"
```

#### Step 5.3: Detect eval() and Dynamic Code Execution

```bash
echo "=== Input Validation: eval() / dynamic execution ==="

grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" \
  $GREP_EXCLUDES \
  -iE '\beval\s*\(|\bnew\s+Function\s*\(|exec\s*\(\s*(f['"'"'"]|['"'"'"].*\+)' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_input_eval.txt || true
EVAL_COUNT=$(wc -l < /tmp/sa_input_eval.txt | tr -d ' ')
echo "eval/dynamic execution patterns: $EVAL_COUNT"
```

#### Step 5.4: Detect Validation Middleware Presence

```bash
echo "=== Input Validation: Middleware presence ==="

VALIDATION_MIDDLEWARE=false

# Zod, Joi, Yup, express-validator, class-validator, ajv
if [ -f package.json ]; then
  jq -r \
    '(.dependencies // {}) + (.devDependencies // {}) | keys[]' \
    package.json 2>/dev/null | \
    grep -qE '^(zod|joi|yup|express-validator|class-validator|ajv)$' && \
    VALIDATION_MIDDLEWARE=true
fi

# Python: pydantic, marshmallow, cerberus
if [ -f pyproject.toml ] || [ -f requirements.txt ]; then
  grep -qE '(pydantic|marshmallow|cerberus)' \
    pyproject.toml requirements.txt 2>/dev/null && \
    VALIDATION_MIDDLEWARE=true
fi

echo "Validation middleware present: $VALIDATION_MIDDLEWARE"
```

#### Step 5.5: Compute Score

AI interprets the collected patterns and validation middleware presence.
Accounts for validation libraries that may be present globally but used
inconsistently. Computes a 0-100 score per the Dimension 5 rubric. Write results
to `/tmp/sa_dim_input.json`.

---

### Phase 6: Authentication/Authorization Audit

Only runs if `DIMENSIONS` includes `auth-authz` or is `all`.

#### Step 6.1: Detect Hardcoded Credentials in Code

```bash
echo "=== Auth: Hardcoded Credentials ==="

grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.java" --include="*.kt" \
  $GREP_EXCLUDES \
  -iE '(username|user|login)\s*[:=]\s*['"'"'"]admin['"'"'"]|password\s*[:=]\s*['"'"'"](password|admin|123456|secret|letmein)['"'"'"]' \
  "$ASSESS_PATH" 2>/dev/null | \
  grep -vE '(test|spec|mock|example|placeholder|TODO)' \
  > /tmp/sa_auth_hardcred.txt || true
HARDCRED_COUNT=$(wc -l < /tmp/sa_auth_hardcred.txt | tr -d ' ')
echo "Hardcoded credential patterns: $HARDCRED_COUNT"
```

#### Step 6.2: Detect JWT Misconfigurations

```bash
echo "=== Auth: JWT Misconfigurations ==="

# alg: none bypass
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" \
  $GREP_EXCLUDES \
  -iE '['"'"'"](alg|algorithm)['"'"'"]\s*:\s*['"'"'"]none['"'"'"]|algorithms\s*:\s*\[\s*['"'"'"]none['"'"'"]' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_auth_jwt_none.txt || true
JWT_NONE=$(wc -l < /tmp/sa_auth_jwt_none.txt | tr -d ' ')
echo "JWT alg:none bypass patterns: $JWT_NONE"

# jwt.verify called without secret/key argument check
grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  $GREP_EXCLUDES \
  -iE 'jwt\.verify\s*\([^,)]+\s*,\s*(null|undefined|'"'"''"'"'|"")' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_auth_jwt_nosecret.txt || true
JWT_NOSECRET=$(wc -l < /tmp/sa_auth_jwt_nosecret.txt | tr -d ' ')
echo "JWT verify without secret: $JWT_NOSECRET"
```

#### Step 6.3: Detect Insecure Session Configuration

```bash
echo "=== Auth: Session Configuration ==="

grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" \
  $GREP_EXCLUDES \
  -iE 'httpOnly\s*:\s*false|secure\s*:\s*false|sameSite\s*:\s*['"'"'"]none['"'"'"]' \
  "$ASSESS_PATH" 2>/dev/null | \
  grep -vE '(test|spec|mock|example)' \
  > /tmp/sa_auth_session.txt || true
SESSION_ISSUES=$(wc -l < /tmp/sa_auth_session.txt | tr -d ' ')
echo "Insecure session attribute patterns: $SESSION_ISSUES"
```

#### Step 6.4: Detect Auth Middleware Presence

```bash
echo "=== Auth: Middleware presence ==="

AUTH_MIDDLEWARE=false

# passport, express-jwt, next-auth, auth0, better-auth, firebase-admin
if [ -f package.json ]; then
  jq -r \
    '(.dependencies // {}) | keys[]' \
    package.json 2>/dev/null | \
    grep -qE '^(passport|express-jwt|next-auth|@auth0/|firebase-admin|better-auth|jose|jsonwebtoken)$' && \
    AUTH_MIDDLEWARE=true
fi

echo "Auth middleware/library present: $AUTH_MIDDLEWARE"
```

#### Step 6.5: Compute Score

AI interprets the collected patterns in context. Routes in test files, auth
middleware global configuration, and framework-level auth (e.g., Next.js
middleware) may resolve apparent issues. Computes a 0-100 score per the
Dimension 6 rubric. Write results to `/tmp/sa_dim_auth.json`.

---

### Phase 7: Configuration Security Audit

Only runs if `DIMENSIONS` includes `config-security` or is `all`.

#### Step 7.1: Detect Debug Mode in Committed Config

```bash
echo "=== Config: Debug Mode ==="

grep -rn --include="*.yaml" --include="*.yml" --include="*.json" \
  --include="*.toml" --include="*.env" --include="*.cfg" \
  --include="*.conf" --include="*.ini" \
  $GREP_EXCLUDES \
  -iE '(debug\s*[:=]\s*true|NODE_ENV\s*=\s*development|FLASK_DEBUG\s*=\s*1|DEBUG\s*=\s*True)' \
  "$ASSESS_PATH" 2>/dev/null | \
  grep -vE '(.env\.example|.env\.sample|.env\.template)' \
  > /tmp/sa_config_debug.txt || true
DEBUG_COUNT=$(wc -l < /tmp/sa_config_debug.txt | tr -d ' ')
echo "Debug mode patterns in committed config: $DEBUG_COUNT"
```

#### Step 7.2: Detect Permissive CORS

```bash
echo "=== Config: Permissive CORS ==="

grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.java" \
  $GREP_EXCLUDES \
  -iE 'origin\s*:\s*['"'"'"]\*['"'"'"]|Access-Control-Allow-Origin.*\*|cors\s*\(\s*\{[^}]*origin\s*:\s*true' \
  "$ASSESS_PATH" 2>/dev/null | \
  grep -vE '(test|spec|mock|example|localhost)' \
  > /tmp/sa_config_cors.txt || true
CORS_COUNT=$(wc -l < /tmp/sa_config_cors.txt | tr -d ' ')
echo "Permissive CORS patterns: $CORS_COUNT"
```

#### Step 7.3: Detect Missing Security Headers

```bash
echo "=== Config: Security Headers ==="

SECURITY_HEADERS_PRESENT=false

# helmet (Node.js), django-csp (Python), secure headers middleware
if [ -f package.json ]; then
  jq -r '(.dependencies // {}) | keys[]' package.json 2>/dev/null | \
    grep -qE '^(helmet|express-security|fastify-helmet)$' && \
    SECURITY_HEADERS_PRESENT=true
fi

if [ -f pyproject.toml ] || [ -f requirements.txt ]; then
  grep -qE '(django-csp|django-security|secure)' \
    pyproject.toml requirements.txt 2>/dev/null && \
    SECURITY_HEADERS_PRESENT=true
fi

echo "Security headers middleware present: $SECURITY_HEADERS_PRESENT"
```

#### Step 7.4: Detect Exposed Admin/Debug Endpoints

```bash
echo "=== Config: Exposed Admin Endpoints ==="

grep -rn --include="*.ts" --include="*.tsx" --include="*.js" \
  --include="*.jsx" --include="*.py" --include="*.go" \
  --include="*.java" --include="*.rb" \
  $GREP_EXCLUDES \
  -iE '['"'"'"](/admin|/debug|/internal|/metrics|/actuator|/health/debug)['"'"'"]' \
  "$ASSESS_PATH" 2>/dev/null \
  > /tmp/sa_config_admin.txt || true
ADMIN_ENDPOINTS=$(wc -l < /tmp/sa_config_admin.txt | tr -d ' ')
echo "Admin/debug endpoint definitions: $ADMIN_ENDPOINTS"
```

#### Step 7.5: Compute Score

AI interprets config findings. Admin endpoints protected behind auth middleware
and debug mode only in non-committed `.env.example` files should not penalize
the score. Computes a 0-100 score per the Dimension 7 rubric. Write results to
`/tmp/sa_dim_config.json`.

---

### Phase 8: Scoring & Report

#### Step 8.1: Compute Composite Score

Compute the overall security score as a weighted average:

```text
overall = weighted_average(
  dependency_vulnerabilities * 0.20,
  secret_detection           * 0.20,
  owasp_top10                * 0.15,
  cryptographic_health       * 0.10,
  input_validation           * 0.15,
  auth_authz                 * 0.10,
  config_security            * 0.10
)
```

If a dimension was skipped (tool unavailable or not selected), redistribute its
weight proportionally among the assessed dimensions.

#### Step 8.2: Classify Security Status

| Score Range | Status    | Meaning                                             |
| ----------- | --------- | --------------------------------------------------- |
| 81-100      | Excellent | Strong security posture, no critical concerns       |
| 61-80       | Good      | Solid foundation, minor improvements recommended    |
| 41-60       | Fair      | Notable risks present, remediation recommended soon |
| 21-40       | Poor      | Significant vulnerabilities, prompt action required |
| 0-20        | Critical  | Severe risks, immediate remediation required        |

#### Step 8.3: Generate Top Recommendations

From findings across all dimensions, identify the top 5 most impactful security
improvements. Sort by severity (critical first), then by effort (low effort
first within the same severity). Each recommendation includes:

- Specific action to take
- CWE reference (where applicable)
- Expected risk reduction
- Effort level (low/medium/high)

#### Step 8.4: Write JSON Report

Write the structured report to this checkout's `reports/security-audit.json`
(`nightgauge layout path checkout reports/security-audit.json`, inside the git
directory, ADR-024 § 7), or to the custom `--output` path:

```json
{
  "schema_version": "1.0",
  "assessment_date": "2026-02-21T00:00:00Z",
  "codebase": {
    "name": "project-name",
    "root_path": "/path/to/project",
    "ecosystems": ["nodejs"],
    "is_monorepo": false,
    "packages": []
  },
  "summary": {
    "overall_security_score": 72,
    "status": "good",
    "dimensions_assessed": 7,
    "dimensions_skipped": 0,
    "total_findings": 15,
    "findings_by_severity": {
      "critical": 0,
      "high": 2,
      "medium": 5,
      "low": 6,
      "info": 2
    }
  },
  "dimensions": {
    "dependency_vulnerabilities": {
      "score": 65,
      "status": "fair",
      "weight": 0.2,
      "findings": [
        {
          "severity": "high",
          "title": "CVE-2024-XXXXX in package-name",
          "description": "Remote code execution vulnerability in package-name <2.0.0",
          "cwe": "CWE-94",
          "cve": "CVE-2024-XXXXX",
          "location": "package.json",
          "recommendation": "Upgrade package-name to >=2.0.0",
          "code_example": "npm install package-name@latest"
        }
      ],
      "metrics": {
        "vulnerability_count": {
          "critical": 0,
          "high": 2,
          "medium": 3,
          "low": 1
        },
        "audit_tool_used": "npm audit",
        "total_dependencies": 87,
        "skipped": false
      }
    },
    "secret_detection": {
      "score": 80,
      "status": "good",
      "weight": 0.2,
      "findings": [],
      "metrics": {
        "generic_key_value_matches": 1,
        "pem_key_matches": 0,
        "aws_key_matches": 0,
        "jwt_token_matches": 0,
        "connection_string_matches": 1,
        "committed_dotenv_files": 0,
        "gitignore_covers_env": true,
        "gitignore_covers_keys": true
      }
    },
    "owasp_top10": {
      "score": 75,
      "status": "good",
      "weight": 0.15,
      "findings": [],
      "metrics": {
        "sql_injection_patterns": 0,
        "command_injection_patterns": 1,
        "path_traversal_patterns": 0,
        "ssrf_patterns": 0,
        "xss_patterns": 2,
        "unsafe_deserialization_patterns": 0
      }
    },
    "cryptographic_health": {
      "score": 90,
      "status": "excellent",
      "weight": 0.1,
      "findings": [],
      "metrics": {
        "weak_algorithm_calls": 0,
        "node_weak_hash_calls": 0,
        "hardcoded_iv_patterns": 0,
        "non_tls_config_urls": 0
      }
    },
    "input_validation": {
      "score": 60,
      "status": "fair",
      "weight": 0.15,
      "findings": [],
      "metrics": {
        "unparameterized_query_patterns": 3,
        "unvalidated_input_patterns": 8,
        "eval_dynamic_execution_patterns": 0,
        "validation_middleware_present": true
      }
    },
    "auth_authz": {
      "score": 85,
      "status": "excellent",
      "weight": 0.1,
      "findings": [],
      "metrics": {
        "hardcoded_credential_patterns": 0,
        "jwt_alg_none_patterns": 0,
        "jwt_no_secret_patterns": 0,
        "insecure_session_patterns": 1,
        "auth_middleware_present": true
      }
    },
    "config_security": {
      "score": 70,
      "status": "good",
      "weight": 0.1,
      "findings": [],
      "metrics": {
        "debug_mode_patterns": 1,
        "permissive_cors_patterns": 0,
        "security_headers_present": true,
        "admin_endpoint_count": 2
      }
    }
  },
  "health_check_integration": {
    "health_report_path": ".git/nightgauge-worktree/reports/health-report.json",
    "dependency_overlap": true,
    "health_check_dep_score": 65,
    "note": "Dependency vulnerability data cross-referenced with health-check report"
  },
  "top_recommendations": [
    {
      "priority": 1,
      "action": "Upgrade package-name to >=2.0.0 to resolve CVE-2024-XXXXX (RCE)",
      "cwe": "CWE-94",
      "cve": "CVE-2024-XXXXX",
      "impact": "Eliminate remote code execution risk",
      "effort": "low",
      "dimension": "dependency_vulnerabilities"
    }
  ],
  "created_at": "2026-02-21T00:00:00Z"
}
```

Build the JSON in a temporary file, validate it, then write it. The default
report goes through `nightgauge layout write`; never write under `.git` by
path:

```bash
REPORT_TMP="$(mktemp)"
# ... write the JSON report to "$REPORT_TMP" ...
python3 -m json.tool "$REPORT_TMP" > /dev/null || { echo "Invalid report JSON" >&2; exit 1; }
if [ -n "$OUTPUT_FILE" ]; then
  cp "$REPORT_TMP" "$OUTPUT_FILE"
  REPORT_PATH="$OUTPUT_FILE"
else
  REPORT_PATH="$(nightgauge layout write checkout reports/security-audit.json --from "$REPORT_TMP")"
fi
rm -f "$REPORT_TMP"
```

#### Step 8.5: Write Markdown Summary

Output a human-readable security report:

```text
SECURITY AUDIT REPORT
================================================================

Project: project-name
Ecosystems: nodejs
Assessment Date: 2026-02-21
Monorepo: No

OVERALL SECURITY SCORE: 72/100 [GOOD]
================================================================

DIMENSION SCORES
----------------------------------------------------------------
  Dependency Vulnerabilities: ████████████░░░░ 65  [FAIR]
  Secret Detection:           ████████████████ 80  [GOOD]
  OWASP Top 10:               ███████████████░ 75  [GOOD]
  Cryptographic Health:       █████████████████ 90  [EXCELLENT]
  Input Validation:           ████████████░░░░ 60  [FAIR]
  Auth/Authorization:         █████████████████ 85  [EXCELLENT]
  Configuration Security:     ██████████████░░ 70  [GOOD]

FINDINGS (15 total: 0 critical, 2 high, 5 medium, 6 low, 2 info)
----------------------------------------------------------------

  [HIGH] Dependency Vulnerabilities: CVE-2024-XXXXX in package-name
    -> Upgrade package-name to >=2.0.0
    -> CWE-94 | Effort: Low

  [MEDIUM] Input Validation: 3 unparameterized SQL query patterns
    -> Use parameterized queries or a query builder (e.g., Knex, Prisma)
    -> CWE-89 | Effort: Medium

  [LOW] Configuration Security: Debug mode flag in committed config
    -> Move debug settings to .env (gitignored) or environment variables
    -> Effort: Low

TOP RECOMMENDATIONS (sorted by severity, then effort)
----------------------------------------------------------------
  1. [HIGH] Upgrade package-name to resolve RCE CVE (low effort)
  2. [MEDIUM] Parameterize 3 raw SQL queries (medium effort)
  3. [LOW] Move debug config to environment variables (low effort)

HEALTH CHECK CROSS-REFERENCE
----------------------------------------------------------------
  Health check dependency score: 65 — consistent with audit findings

----------------------------------------------------------------
Report saved: .git/nightgauge-worktree/reports/security-audit.json
```

If `--format json`, write only JSON. If `--format summary`, output only the
markdown. If `--format both`, write JSON and output the markdown summary.

Findings below the `--severity` threshold are omitted from the markdown summary
but always included in the JSON report.

---

### Phase 9: Monorepo Aggregation (Conditional)

Only runs if `IS_MONOREPO=true` and `--package` was NOT specified.

#### Step 9.1: Per-Package Assessment

For each package in `PACKAGES`:

1. Run Phases 1-7 scoped to the package directory
2. Compute per-package scores

Use the `Task` tool with `model: "haiku"` to spawn parallel subagents for
independent package assessments. Each subagent receives: package path, ecosystem
detected, dimensions to assess, and severity filter.

#### Step 9.2: Aggregate Scores

Compute aggregate scores using equal-weight averaging across packages:

```text
aggregate_score = sum(package_scores) / package_count
```

Report the worst-scoring package for each dimension as the primary risk signal.

#### Step 9.3: Per-Package Breakdown in Report

Add per-package section to both JSON and markdown reports:

```json
{
  "packages": [
    {
      "name": "packages/api-server",
      "overall_security_score": 58,
      "status": "fair",
      "dimensions": { "...per-dimension scores..." }
    },
    {
      "name": "packages/frontend",
      "overall_security_score": 82,
      "status": "excellent",
      "dimensions": { "...per-dimension scores..." }
    }
  ]
}
```

---

## Health Check Integration

The security audit cross-references the health-check report when available in
this checkout's `reports/health-report.json`
(`nightgauge layout path checkout reports/health-report.json`). This enables:

- **Dependency overlap**: The health-check `dependency_health` dimension runs
  `npm audit` too. If the health-check report is fresh (< 24 hours), the
  security audit may reuse its audit data rather than re-running the tool.
- **Context enrichment**: Lockfile presence, outdated dependency count, and
  total dependency count from the health-check feed into security dimension
  score computation.
- **Consistent reporting**: Both reports are stored in this checkout's
  `reports/` directory for easy side-by-side comparison.

To generate both reports in sequence:

```bash
/nightgauge:health-check
/nightgauge:security-audit
```

---

## Key Regex Patterns Reference

| Pattern Category  | Regex                                                                         | Rationale                                  |
| ----------------- | ----------------------------------------------------------------------------- | ------------------------------------------ |
| Generic secrets   | `(?i)(api[_-]?key\|secret\|password\|token\|auth)\s*[:=]\s*['"][^'"]{8,}['"]` | Catches most key=value credential patterns |
| AWS access key    | `AKIA[0-9A-Z]{16}`                                                            | AWS access key ID format                   |
| PEM private key   | `BEGIN (RSA \|EC \|DSA \|OPENSSH )?PRIVATE KEY`                               | Catches all common private key PEM headers |
| SQL injection     | `(SELECT\|INSERT\|UPDATE\|DELETE).*\+\s*\w+`                                  | String concatenation in queries            |
| Command injection | `(exec\|spawn\|system\|popen)\s*\(.*(\$\{\|\+\s*\w+\|req\.)`                  | User-controlled input to shell             |
| Path traversal    | `(\.\./\|\.\.\\\\)\|path\.join\s*\(.*req\.`                                   | Directory traversal and unsafe path joins  |
| SSRF              | `(fetch\|axios\|http\.get)\s*\(.*\b(req\.\|params\.\|query\.)`                | User-controlled URLs in HTTP requests      |
| Weak crypto       | `(?i)(md5\|sha1\|des\|rc4\|ecb)\s*\(`                                         | Deprecated/weak algorithm function calls   |
| Hardcoded IV      | `(iv\|salt\|nonce)\s*=\s*['"][0-9a-fA-F]{8,}['"]`                             | Static initialization vectors and salts    |
| JWT alg:none      | `['"]algorithm['"]\s*:\s*['"]none['"]`                                        | JWT algorithm bypass vulnerability         |

---

### Self-Assessment Epilogue

### Self-Assessment Epilogue

**PURPOSE**: Evaluate whether this skill's instructions matched reality during
this execution. This phase is **non-blocking** — skip entirely if any main phase
failed. A perfectly working skill produces **no output** from this phase.

> See [docs/SKILL_SELF_ASSESSMENT.md](https://github.com/nightgauge/nightgauge/blob/main/docs/SKILL_SELF_ASSESSMENT.md) for
> the full strategy, synthesis algorithm, and integration architecture.

#### When to Skip

- Any prior phase exited with an error before completing
- The skill was cancelled or timed out
- Running in `--dry-run` mode

#### Step 1: Evaluate Execution Friction

Review the execution that just completed. Answer these questions honestly:

1. **Command failures**: Did any command, script, or binary call in the skill
   instructions fail? (e.g., script not found, wrong flags, missing binary)
2. **Workarounds**: Did you have to deviate from the skill instructions to
   accomplish the goal? (e.g., used `gh` directly because a referenced script
   didn't exist, skipped a step that referenced a nonexistent file)
3. **Stale references**: Did any file path, function name, API endpoint, or tool
   referenced in the instructions not exist in the current codebase?
4. **Unclear instructions**: Were any instructions ambiguous enough that you had
   to guess at the intended behavior?
5. **Missing instructions**: Was there a significant step you had to figure out
   on your own that should have been documented in this skill?

**If ALL answers are "no" — write nothing and complete the skill normally.** The
goal is silence when everything works.

#### Step 2: Write Assessment Record

Only if friction was detected in Step 1. Write a single JSON file,
`assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json` in the clone's pipeline state
directory, through the binary (JSON on stdin; never a direct file write):

```bash
ISSUE_NUMBER="${NIGHTGAUGE_ISSUE_NUMBER:-$(git branch --show-current | sed -n 's#^[^/]*/\([0-9]*\)-.*#\1#p')}"
: "${ISSUE_NUMBER:?set NIGHTGAUGE_ISSUE_NUMBER or check out the issue branch}"
nightgauge layout write pipeline "assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json" <<'JSON'
{ ... the record below ... }
JSON
```

The assessment record MUST follow this schema:

```json
{
  "schema_version": "1",
  "skill": "{STAGE_NAME}",
  "skill_file": "skills/nightgauge-{STAGE_NAME}/SKILL.md",
  "issue_number": 42,
  "timestamp": "2026-03-10T14:30:00Z",
  "friction": [
    {
      "type": "command_failure",
      "severity": "high",
      "description": "hooks/lib/add-to-project.sh not found — script was deleted",
      "skill_line_hint": "claude-plugins/nightgauge/hooks/lib/add-to-project.sh <issue-number>",
      "actual_resolution": "Used gh api graphql to add issue to project board directly",
      "suggested_fix": "Replace with: nightgauge project add <issue-number>"
    }
  ]
}
```

**Friction types**: `command_failure`, `workaround`, `stale_reference`,
`unclear_instruction`, `missing_instruction`

**Severity levels**:

- `high` — instruction is **broken**. Required manual workaround to complete.
- `medium` — instruction is **misleading**. Agent adapted without user help.
- `low` — instruction is **suboptimal**. No functional impact.

**Rules**:

- **One record per execution** — multiple friction items go in the `friction`
  array, not separate files.
- **Be specific** — quote the exact instruction text that was wrong. Not "some
  commands didn't work" but "Step 5.2 calls `hooks/lib/add-to-project.sh` but
  this script was deleted in commit 65915701."
- **Suggest the fix** — every finding MUST include `suggested_fix` with the
  concrete SKILL.md change needed. Not "update the docs" but "replace
  `hooks/lib/add-to-project.sh <N>` with `nightgauge project add <N>`."
- **Don't invent friction** — only report issues you actually encountered during
  this execution. Do not speculate about potential problems.

#### Step 3: Validate and Complete

```bash
# Validate JSON if written
ISSUE_NUMBER="${NIGHTGAUGE_ISSUE_NUMBER:-$(git branch --show-current | sed -n 's#^[^/]*/\([0-9]*\)-.*#\1#p')}"
: "${ISSUE_NUMBER:?set NIGHTGAUGE_ISSUE_NUMBER or check out the issue branch}"
ASSESSMENT_FILE="$(nightgauge layout path pipeline "assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json")"
if [ -f "$ASSESSMENT_FILE" ]; then
  python3 -m json.tool "$ASSESSMENT_FILE" > /dev/null 2>&1 || \
    echo "WARNING: Assessment record is not valid JSON" >&2
fi
```


---

## Output Format

### JSON Schema

See Phase 8 Step 8.4 for the complete JSON report structure.

### Report Files

| File                                   | Format   | When Written            |
| -------------------------------------- | -------- | ----------------------- |
| `checkout reports/security-audit.json` | JSON     | `--format json/both`    |
| Console output                         | Markdown | `--format summary/both` |

`checkout` paths are inside this checkout's git directory (ADR-024 § 7):
`nightgauge layout path checkout reports/security-audit.json` prints the
resolved path.

---

## Error Handling

| Condition                    | Action                                                              |
| ---------------------------- | ------------------------------------------------------------------- |
| No ecosystem detected        | Proceed with generic file analysis (regex scans only)               |
| Audit tool not installed     | Skip dimension metric, note tool name in findings                   |
| `--dimensions` invalid value | Error with valid dimension list                                     |
| Assessment path not found    | Error with path not found message                                   |
| `jq` not installed           | Error with install instructions                                     |
| Permission denied on files   | Skip inaccessible files, note count in report                       |
| Large codebase timeout       | Use `head`/`--max-count` limits, sample files                       |
| Monorepo package not found   | Warning, skip package, continue with others                         |
| `--severity` invalid value   | Error with valid severity levels: info, low, medium, high, critical |
| All dimensions skipped       | Error: at least one dimension must be assessed                      |

---

## Pipeline Position

```text
UTILITIES (not part of main pipeline)

/nightgauge:health-check ─────────────────────┐
       |                                            |
  Standalone utility — run anytime           (optional input)
  Writes: checkout reports/health-report.json  |
                                                    v
                                  /nightgauge:security-audit
                                         |
                                    Standalone utility — run anytime
                                    Reads: Codebase files (read-only)
                                    Reads: checkout reports/health-report.json (optional)
                                    Writes: checkout reports/security-audit.json
```

This is a standalone utility skill. It does not affect pipeline state and can be
run at any time without interfering with active pipeline runs.

---

## Configuration

This skill reads configuration from `.nightgauge/config.yaml` if present:

| Config Key                       | Default | Description                |
| -------------------------------- | ------- | -------------------------- |
| `security_audit.default_format`  | `both`  | Default `--format` value   |
| `security_audit.skip_audit`      | `false` | Default for `--skip-audit` |
| `security_audit.output_path`     | auto    | Default JSON output path   |
| `security_audit.severity_filter` | `low`   | Default `--severity` value |

---

**Author:** nightgauge **License:** Apache-2.0

Files in this skill

  • CHANGELOG.md1.9 KB
  • SKILL.md64.8 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…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.