Reference · 6 min read

Write custom security checks

The 70+ services Noxen flags out of the box cover the common homelab fleet. For in-house admin UIs, vendor-specific tools, or anything not yet in AdminSurfaceCatalog, drop a JSON file into the custom-checks directory and Noxen runs it on every scan.

Where checks live

~/Library/Application Support/app.noxen/custom-checks/

Every *.json file in that directory is parsed as a single check on Noxen launch and on demand via Scan → Reload custom checks (⌘⇧U). Subdirectories are searched recursively, so you can group checks by team / service / client.

HTTP check schema

{
  "id": "internal-grafana-no-auth",
  "category": "exposedAdmin",
  "severity": "high",
  "title": "Internal Grafana with anonymous viewer",
  "kind": "http",
  "ports": [3000, 3001],
  "paths": ["/api/health", "/login"],
  "markers": [
    { "type": "header_present", "header": "Set-Cookie", "value": "grafana_session" },
    { "type": "body_contains", "value": "Anonymous Org" }
  ],
  "remediation": "Set [auth.anonymous] enabled = false in grafana.ini and restart."
}

Field reference:

id
Unique string. Used in the finding identifier so duplicates from re-scans are deduplicated correctly.
category
One of exposedAdmin, missingHeader, weakTLS, customCheck (the catch-all). Drives which UI tab the finding shows up in.
severity
critical | high | medium | low | info.
title
Human-readable finding title.
kind
"http" for HTTP/HTTPS GET probes; "tcp" for raw-socket banner / send-and-match probes (see below).
ports
Array of TCP port numbers to probe. Skipped if the host's port scan didn't find any of these open.
paths
Array of HTTP paths to GET. The first path that returns 2xx and matches every marker triggers the finding.
markers
Array of conditions, AND-ed. All must match for the finding to fire. Marker types:
  • {"type":"status","value":200} — exact HTTP status code.
  • {"type":"body_contains","value":""} — case-insensitive substring match in response body.
  • {"type":"body_regex","pattern":""} — Swift NSRegularExpression in response body.
  • {"type":"header_present","header":"X-Foo"} — header exists at all.
  • {"type":"header_value","header":"Server","value":"nginx"} — exact header value match.
  • {"type":"header_contains","header":"Set-Cookie","value":"grafana_session"} — substring match in header.
remediation
One-paragraph guidance shown in the finding. Use the same voice as the built-in remediation — concrete, command-level if possible.

TCP check schema

For non-HTTP services where you want to detect a banner or response from a specific send. Example: a custom Erlang admin port that responds with a known string:

{
  "id": "erlang-cookie-disabled",
  "category": "exposedAdmin",
  "severity": "critical",
  "title": "Erlang distribution port without cookie auth",
  "kind": "tcp",
  "ports": [4369, 25672],
  "send_hex": "0000",
  "expect_contains": "epmd_protocol",
  "remediation": "Set ERL_AFLAGS to require cookie auth and bind to localhost."
}
send_hex (optional)
Hex-encoded bytes to send before reading. Omit for a pure banner-grab probe (just connect, read, match).
expect_contains
Substring expected in the response. Case-sensitive.
expect_regex (optional alternative)
Regex match against the response. Use this when the banner format varies by version.

Reload after editing

Scan → Reload custom checks in the menu bar, or ⌘⇧U. Noxen re-parses every JSON file, surfaces validation errors as banner notifications (with file path + line / field hint), and updates the in-memory check registry. Re-scans use the new checks immediately.

Validation errors

A malformed JSON file does NOT abort other checks — Noxen logs the parse error, skips that file, and continues. The Reload action surfaces the error as a banner with the offending file path so you can fix and reload again. Common errors:

Sharing checks

The custom-checks directory is just JSON files — version-control it, share via Git, drop into MSP-managed installs. Future Noxen versions may ship a "community check pack" auto-installer; for now, cp -r your-checks/ ~/Library/Application\ Support/app.noxen/custom-checks/ is enough.