doctor

package
v1.1.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 18, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

S77-T08: doctor --deep check for orphaned Hasura remote schemas.

A remote schema is "orphaned" when it is registered in Hasura metadata but the plugin that provides it is no longer installed. This can happen after `nself plugin uninstall <name>` if Hasura metadata was not cleaned up, or after a manual plugin removal. Orphaned remote schemas cause Hasura to emit schema-load errors on startup and break the GraphQL API surface.

Detection strategy:

  1. Read NSELF_HASURA_GRAPHQL_URL (or derive from HASURA_GRAPHQL_URL) to locate the running Hasura instance.
  2. Call the Hasura Metadata API to export remote_schemas (POST /v1/metadata with {"type":"export_metadata","args":{}}). Requires HASURA_GRAPHQL_ADMIN_SECRET.
  3. For each remote schema entry, derive the expected plugin name from the remote schema's URL: internal plugin URLs follow the pattern http://host:PORT where PORT is registered in PLUGIN_<NAME>_INTERNAL_URL.
  4. Cross-reference against loaded plugins: env var NSELF_PLUGINS_LOADED (comma- separated list set by `nself build`) and per-plugin PLUGIN_<NAME>_INTERNAL_URL.
  5. Emit WARN for each orphan found; PASS if none; SKIP if Hasura is unreachable.

The check is read-only — it never modifies Hasura metadata. S77-T08

legal_coppa.go implements the LEGAL-COPPA-01 deep doctor check.

LEGAL-COPPA-01 verifies that the COPPA parental-consent flow shipped in the family plugin is wired correctly before the nFamily product can be marked production-ready.

The check is layered:

pass — migration present, consent secret env set, TTL within policy
warn — migration present but FAMILY_CONSENT_HMAC_SECRET unset, or TTL
       above the policy ceiling, or the family plugin is loaded without
       the migration on disk
fail — family plugin loaded AND migration file missing entirely (the
       parental-consent flow cannot work without the schema)
skip — family plugin not loaded; the check is not applicable

The Hasura row-level accessibility of np_parental_consents is verified by the existing PERM-HASURA-01 / PERM-RLS-01 checks (np_* tables are blanket covered there). LEGAL-COPPA-01 stays focused on the COPPA-specific preconditions: schema present, signing secret set, TTL within policy.

S12.T06 follow-up.

legal_gdpr_a9.go implements the LEGAL-GDPR-A9-01 deep doctor check.

LEGAL-GDPR-A9-01 verifies that the GDPR Article 9 special-category consent flow shipped in the family plugin is wired correctly before the nFamily product can be marked production-ready.

The check is layered:

pass — np_family_consents migration on disk AND the privacy page
       contains the GDPR Article 9 disclosure section
warn — migration on disk but the privacy page is missing the Article 9
       disclosure, or NSELF_DPO_EMAIL is unset (recommended for any
       deployment processing special-category data)
fail — family plugin loaded AND np_family_consents migration absent
skip — family plugin not loaded; the check is not applicable

The runtime accessibility of np_family_consents is covered by the existing PERM-RLS-01 check (np_* RLS sweep). LEGAL-GDPR-A9-01 stays focused on Article 9 preconditions: schema present, privacy disclosure present, DPO contact recorded.

S12.T07 follow-up.

S69-T05: doctor check for ai+moderation wiring on public-bound deployments.

If the ai plugin is loaded AND the deployment binding is non-loopback AND the moderation plugin is NOT loaded, this emits a WARN. Severity is WARN (not FAIL) because self-hosted single-user setups are legitimate.

S12.T01 — doctor check OBS-SCRAPE-01.

When any ɳSentry plugin (nself-uptime-monitor, nself-status-page, nself-incident-mgmt, nself-alert-router, nself-slo-tracker, nself-synthetic-monitor, nself-rum) is installed, the generated monitoring/prometheus.yml MUST contain a scrape-target stanza for each installed plugin (job name "nsentry-<slug>").

Severity: WARN. Missing stanzas mean Prometheus is not scraping the plugin's /metrics endpoint, but the plugin itself still runs — degraded observability, not broken functionality.

Package doctor — ops_drill.go: OPS-DRILL-01 check.

Verifies that a successful `nself backup drill` ran within the last 7 days. The drill log lives at <projectDir>/.nself/drill-log.json (JSON Lines format, written by internal/backup.Drill). The check is read-only and never invokes the drill itself — that would be a privileged operation gated by SSH.

Status semantics:

  • PASS: most recent successful drill within 7 days
  • WARN: most recent successful drill 7–14 days old (operator nudge)
  • FAIL: no successful drill found OR most recent > 14 days

The fix-cmd suggests `nself backup drill` so operators can run it manually or wire the weekly cron via scripts/dr-drill.sh.

Package doctor — redact_audit.go: doctor check OBS-REDACT-01.

Verifies that a synthetic telemetry-shaped payload, after passing through observability.Redact, contains no patterns matching the canonical PII regexes (email, IPv4 [non-loopback], IPv6 [non-loopback], JWT). Catches regressions in redact.go where a pattern is silently removed or weakened.

Severity: WARN. A redaction regression does not break the CLI but does degrade the privacy guarantees documented in .claude/docs/operations/telemetry-privacy.md.

Package doctor — rls_check_nsentry.go

ɳSentry-specific RLS checks (NSENTRY-RLS-01..07).

When any of the 7 ɳSentry baseline plugins is installed, nself doctor --deep verifies that its np_* tables have:

  • RLS enabled (relrowsecurity = true)
  • At least one policy
  • FORCE RLS when a tenant_id column is present
  • A Hasura row filter for tenant_id ({"tenant_id":{"_eq":"X-Hasura-Tenant-Id"}})

Each of the 7 plugins gets its own check ID (NSENTRY-RLS-01..07) and runs only when that plugin's plugin.json is present in pluginDir.

Severity: CRITICAL — RLS misconfiguration on ɳSentry tables exposes uptime, incident, and SLO data across tenants.

Security-Always-Free Doctrine: these checks run without a license.

Package doctor provides comprehensive health check sections for nself doctor.

Index

Constants

View Source
const (
	// DefaultPostgresMaxConnections is the Postgres default max_connections setting.
	// Operators should raise this in POSTGRES_MAX_CONNECTIONS when running many services.
	DefaultPostgresMaxConnections = 100

	// PoolCapCheckID is the canonical check ID referenced in docs and Alertmanager.
	PoolCapCheckID = "PERF-POOL-01"
)
View Source
const (
	// HasuraMetaCheckID is the canonical check ID referenced in docs and wiki.
	HasuraMetaCheckID = "PERM-HASURA-01"
)
View Source
const (
	// RLSCheckID is the canonical check ID referenced in docs, wiki, and Alertmanager.
	RLSCheckID = "PERM-RLS-01"
)

Variables

This section is empty.

Functions

func RecommendedPoolSize added in v1.0.11

func RecommendedPoolSize(postgresMaxConnections, numServices int) int

RecommendedPoolSize computes the recommended per-service MaxConns cap.

MaxConns = min(10, floor(postgres_max_connections / num_services))

Edge cases:

  • numServices=0 → returns 10 (no services, no constraint)
  • very large max_connections → capped at 10 (prevents runaway allocations)
  • numServices > max_connections → returns 1 (absolute minimum)

func RecordCITokenRotation added in v1.0.14

func RecordCITokenRotation(projectDir string) error

RecordCITokenRotation writes a new timestamp to the rotation log. Called by `nself doctor --record-ci-token-rotation`. Creates parent dirs as needed.

Types

type CheckResult

type CheckResult struct {
	Section string `json:"section"`
	Name    string `json:"name"`
	Status  string `json:"status"` // pass, warn, fail
	Message string `json:"message"`
	FixCmd  string `json:"fix_cmd,omitempty"` // suggested fix command
}

CheckResult holds the outcome of a single diagnostic check.

func BackupChecks

func BackupChecks(_ context.Context, projectDir string) []CheckResult

BackupChecks verifies backup health.

func CheckAdminBind added in v1.0.11

func CheckAdminBind(ctx context.Context) CheckResult

CheckAdminBind verifies that the nself-admin container is bound to 127.0.0.1 only (not 0.0.0.0 which would expose it to the LAN). This implements S43-T03 — required by the Service Binding Hard Rule.

func CheckCIToken added in v1.0.14

func CheckCIToken(projectDir string) CheckResult

CheckCIToken is the CI-TOKEN-01 deep doctor check.

It verifies:

  1. NSELF_CI_TOKEN is set (in any env cascade file or the environment itself).
  2. A rotation log exists documenting the last time the token was rotated.
  3. Expiry-proximity check (primary): if an "expires <RFC3339>" entry exists in the rotation log, warn at ≤14d remaining and fail at ≤1d remaining.
  4. Rotation-age check (fallback): when no expiry date is present in the log (legacy entries), fall back to the old 90d soft / 120d hard rotation-age logic.

This check is --deep only. It does NOT attempt to call the GitHub API (no network) — it only inspects the local rotation log.

To update the rotation log after a token rotation:

nself doctor --record-ci-token-rotation [--expires <RFC3339>]

(CLI command delegates to internal/doctor.RecordCITokenRotation.)

func CheckCIVaultSync added in v1.0.14

func CheckCIVaultSync(projectDir string) CheckResult

CheckCIVaultSync is the CI-VAULT-SYNC-01 deep doctor check.

It verifies that vault.env or the live environment contains all critical CI PATs (fail on missing) and 180-day infra tokens (warn on missing).

The check does NOT validate token values — only presence. A token could be present but expired; CI-TOKEN-01 handles rotation-age tracking for NSELF_CI_TOKEN specifically. CI-VAULT-SYNC-01 covers the broader vault coverage question.

func CheckEncryptionKeyScope added in v1.0.11

func CheckEncryptionKeyScope(projectDir string, strict bool) []CheckResult

CheckEncryptionKeyScope verifies that AI_ENCRYPTION_KEY (and any other *_ENCRYPTION_KEY env vars) are set and are not reused across environments. Implements S43-UNDEP-01 + S43-AUDIT-01 (SEC-ENC-01 and SEC-ENC-02).

When strict=true, a shared or default key returns Status="fail". When strict=false (default), it returns Status="warn" — allowing the admin to investigate without blocking a deploy.

func CheckHasuraIntrospection added in v1.0.11

func CheckHasuraIntrospection(_ context.Context) CheckResult

CheckHasuraIntrospection implements SEC-HASURA-INTRO-01. It warns if HASURA_GRAPHQL_DISABLE_INTROSPECTION is not set to "true" in a production environment (NSELF_ENV=production or NODE_ENV=production).

The check is:

  • status "skip": environment is dev/staging or NSELF_ENV is unset/empty
  • status "pass": HASURA_GRAPHQL_DISABLE_INTROSPECTION=true in production
  • status "warn": HASURA_GRAPHQL_DISABLE_INTROSPECTION absent or not "true" in production

The check is read-only — it never modifies Hasura configuration. S70-T06

func CheckHasuraMetadataBackup added in v1.0.14

func CheckHasuraMetadataBackup(backupDir string) CheckResult

CheckHasuraMetadataBackup verifies that a hasura-metadata-*.json file exists in backupDir and is less than 36 hours old (implements BACKUP-METADATA-01).

func CheckHasuraMetadataYAML added in v1.1.0

func CheckHasuraMetadataYAML(_ context.Context, projectDir string, strict bool) []CheckResult

CheckHasuraMetadataYAML implements PERM-HASURA-01.

It walks hasura/metadata/ (and the subdirectory databases/default/tables/ if present) under projectDir, parses every *.yaml file that defines a table, and verifies that each tracked np_* table has the correct user-role row filter.

Rules:

  • np_* table with source_account_id in filter → PASS
  • np_* table with tenant_id in filter → PASS
  • np_* table with neither filter → FAIL (PERM-HASURA-01)
  • np_* table with no user select_permission → FAIL (PERM-HASURA-01)
  • non-np_* table or table with no isolation col → exempt

Pass strict=true to report failures as "fail" (blocks deploy). Default "warn". ctx is reserved for future cancellation; disk reads are synchronous.

func CheckJWTRotation added in v1.0.14

func CheckJWTRotation(projectDir string) CheckResult

CheckJWTRotation implements the JWT-ROT-01 deep doctor check. It verifies:

  1. HASURA_GRAPHQL_JWT_SECRET is set (delegates to CheckJWTSecretPresent for the precise fail/warn/pass logic on where the key lives).
  2. A rotation log exists at the configured path.
  3. The last recorded rotation is within the configured window (default 90d).

This check is only included in --deep mode.

func CheckJWTSecretPresent added in v1.0.7

func CheckJWTSecretPresent(projectDir string) CheckResult

CheckJWTSecretPresent reports whether HASURA_GRAPHQL_JWT_SECRET is defined in any of the project's env files. Semantics:

  • fail when the key is absent from both .env and .env.secrets and no other env file has it either; this is what the spec calls "Hasura enabled but JWT secret absent from both sources".
  • warn when the key is in .env.secrets but missing from .env. This is the nSelf-preferred arrangement (secrets live in .env.secrets which is gitignored and chmod 0600), so it is informational only.
  • pass when the key is present in .env (with or without .env.secrets).

projectDir is the working directory passed through from `nself doctor`.

func CheckLegalCOPPA added in v1.1.0

func CheckLegalCOPPA(projectDir string) CheckResult

CheckLegalCOPPA implements LEGAL-COPPA-01.

func CheckLegalGDPRA9 added in v1.1.0

func CheckLegalGDPRA9(projectDir string) CheckResult

CheckLegalGDPRA9 implements LEGAL-GDPR-A9-01.

func CheckMinioCredentials added in v1.1.8

func CheckMinioCredentials() CheckResult

CheckMinioCredentials reads NSELF_ENV, MINIO_ROOT_USER, and MINIO_ROOT_PASSWORD from the environment and returns a CRITICAL finding when either credential uses the insecure "minioadmin" default in a staging or production deployment. Dev environments are always unblocked.

Check ID: MINIO-CRED-01

func CheckModerationWired added in v1.0.11

func CheckModerationWired(_ context.Context) CheckResult

CheckModerationWired implements S69-T05: warns when the ai plugin is loaded on a public-bound deployment without the moderation plugin also loaded.

Detection logic:

  • ai plugin loaded: NSELF_AI_LOADED=1 OR PLUGIN_AI_INTERNAL_URL non-empty
  • moderation plugin loaded: NSELF_MODERATION_LOADED=1 OR PLUGIN_MODERATION_INTERNAL_URL non-empty
  • public bind: NSELF_BIND_ADDRESS is NOT 127.x / localhost / ::1 / "" (empty = loopback default)

func CheckNSelfAuditScan added in v1.1.2

func CheckNSelfAuditScan(ctx context.Context, projectDir string) []CheckResult

CheckNSelfAuditScan dispatches the NSCAN-001..010 rule set to the nself-audit plugin and returns the per-finding CheckResults.

projectDir is used to compute the env file and docker-compose path. When the plugin is not reachable, returns a single "skipped" pass-result rather than a fail (Security-Always-Free contract — the scan is an enhancement, not a blocker for projects that don't have the plugin installed).

func CheckNSentryRLS added in v1.1.1

func CheckNSentryRLS(ctx context.Context, pluginDir string) []CheckResult

CheckNSentryRLS runs NSENTRY-RLS-01..07 for each installed ɳSentry baseline plugin. A plugin is considered installed when pluginDir/<plugin-name>/plugin.json exists. When none of the 7 baseline plugins are installed, the function returns a single pass result ("nsentry not installed — skipping").

Each per-plugin check delegates to CheckRLSEnforcement (the generic PERM-RLS-01 implementation) and filters to only the np_* tables whose name begins with the plugin's tablePrefix. This avoids re-querying the DB once per plugin; instead a single query fetches all np_* tables and per-plugin results are sliced from that shared list.

Severity escalation: all violations are reported with status "fail" (CRITICAL). This is stricter than the generic PERM-RLS-01 (which uses "warn" by default) because ɳSentry tables hold cross-tenant observability data.

The function accepts pluginDir as a parameter so tests can provide a temp directory without touching the real filesystem.

func CheckOBSScrape added in v1.1.0

func CheckOBSScrape(_ context.Context, projectDir, pluginDir string) CheckResult

CheckOBSScrape implements OBS-SCRAPE-01 — verifies Prometheus scrape config covers every installed ɳSentry plugin.

projectDir is the user's project root (where monitoring/ lives). pluginDir is the plugin install directory (typically ~/.nself/plugins). When either is empty, the check is skipped (returns pass with explanation).

func CheckOPSDrill added in v1.1.0

func CheckOPSDrill(_ context.Context, projectDir string) CheckResult

CheckOPSDrill returns the OPS-DRILL-01 result for projectDir. The function is pure read-only — it never mutates filesystem state.

func CheckOrphanRemoteSchemas added in v1.0.11

func CheckOrphanRemoteSchemas(ctx context.Context) CheckResult

CheckOrphanRemoteSchemas implements S77-T08. It queries Hasura metadata and compares registered remote schemas against the currently installed nself plugins. Any remote schema whose providing plugin is absent is flagged as an orphan.

func CheckPayPalCSVParity added in v1.1.8

func CheckPayPalCSVParity(ctx context.Context) CheckResult

CheckPayPalCSVParity verifies that PayPal environment variables have matching counts for PAYPAL_CLIENT_IDS and PAYPAL_CLIENT_SECRETS when multi-account mode is enabled. Purpose: Prevent silent mispairing of credentials in multi-account PayPal setups. Inputs: Environment variables (PAYPAL_CLIENT_IDS, PAYPAL_CLIENT_SECRETS). Outputs: CheckResult with status (ok/warn) and message naming the count mismatch if any. Constraints: Runs only when PAYPAL_CLIENT_IDS is set (indicating PayPal is installed).

func CheckRLSEnforcement added in v1.0.11

func CheckRLSEnforcement(ctx context.Context, strict bool) []CheckResult

CheckRLSEnforcement implements PERM-RLS-01.

It queries Postgres pg_class for every np_* table and verifies:

(a) RLS is enabled (relrowsecurity = true)
(b) at least one policy exists
(c) PatternTenantScoped tables have FORCE RLS (relforcerowsecurity = true)
(d) tables with tenant_id column have Hasura row filter for that column

Violations are WARN by default. Pass strict=true to escalate to fail.

This check is read-only. It never modifies DB state.

func CheckRedactionCoverage added in v1.1.0

func CheckRedactionCoverage(_ context.Context) CheckResult

CheckRedactionCoverage runs OBS-REDACT-01.

func CheckSDKVersions added in v1.0.14

func CheckSDKVersions(ctx context.Context) []CheckResult

CheckSDKVersions implements the SDK-VERSION-01 deep doctor check. It returns one CheckResult per SDK (4 total), each independently pass / warn / fail so a single drifted SDK does not mask the others.

func CheckSSRF added in v1.0.14

func CheckSSRF(projectDir string) CheckResult

CheckSSRF implements the SSRF-01 deep doctor check. It locates the plugins-pro paid/ directory relative to projectDir and verifies that each plugin service carries effective SSRF guard code (file present + guard symbol present).

projectDir is the nself project working directory (where .env lives). plugins-pro is expected to be a sibling of cli/ inside the nself monorepo.

func DeepChecks

func DeepChecks(ctx context.Context, projectDir string, verbose bool) []CheckResult

DeepChecks runs all 12 subsystem categories for --deep mode.

func DockerDeepChecks

func DockerDeepChecks(ctx context.Context, verbose bool) []CheckResult

DockerDeepChecks verifies daemon, storage driver, dangling images, container health.

func DogfoodChecks added in v1.0.13

func DogfoodChecks(ctx context.Context, projectDir string, verbose bool) []CheckResult

DogfoodChecks runs the full dogfood-only check set. It is invoked from DeepChecks when NSELF_DOGFOOD=1 is set, AND can be invoked directly from CI to gate nself.org's own deploys.

Check IDs (cross-referenced in operations/dogfood-checks.md):

SEC-CSP-01    — CSP headers present on all subapps
SEC-HSTS-01   — HSTS preload value
SEC-AUTH-01   — HttpOnly cookie convention enforced
VENDOR-DEP-01 — no AGPL/SSPL deps in production source
DOGFOOD-SUBAPPS-01 — every shipped subapp has synced sport.json
DOGFOOD-HEX-01     — no hex colors in subapp src/ trees
DOGFOOD-LICENSE-01 — owner license vault path readable

PERM-RLS-01 is intentionally NOT duplicated here; rls_check.go owns it.

func FilterBySection

func FilterBySection(results []CheckResult, section string) []CheckResult

FilterBySection returns only checks matching the given section name.

func FixItEngine

func FixItEngine(ctx context.Context, results []CheckResult) []CheckResult

FixItEngine runs safe auto-fixes for check results that have FixCmd set.

func HardeningChecks added in v1.1.0

func HardeningChecks(ctx context.Context, projectDir string) []CheckResult

HardeningChecks runs all SEC-HARDENING-*, SEC-CORS-01, SEC-OFFLINE-01, SEC-DEVMODE-01, and SEC-METRICS-01 checks. projectDir is the nSelf working directory (same value passed to DeepChecks).

func HasuraChecks

func HasuraChecks(ctx context.Context, verbose bool) []CheckResult

HasuraChecks verifies /healthz 200 and metadata consistency.

func HostChecks

func HostChecks(ctx context.Context, verbose bool) []CheckResult

HostChecks verifies disk, memory, CPU, time sync, kernel.

func LicenseChecks

func LicenseChecks(ctx context.Context, projectDir string, verbose bool) []CheckResult

LicenseChecks verifies license is current and tier matches expected.

func MonitoringChecks

func MonitoringChecks(ctx context.Context) []CheckResult

MonitoringChecks verifies monitoring stack health.

func NginxChecks

func NginxChecks(ctx context.Context, verbose bool) []CheckResult

NginxChecks verifies config test and SSL cert expiry.

func PingChecks

func PingChecks(ctx context.Context, verbose bool) []CheckResult

PingChecks verifies ping.nself.org reachable and license cache fresh.

func PluginHealthChecks

func PluginHealthChecks(ctx context.Context, projectDir string, verbose bool) []CheckResult

PluginHealthChecks verifies every installed plugin's health endpoint.

func PoolCapacityCheck added in v1.0.11

func PoolCapacityCheck(_ context.Context, pools []PoolConfig, postgresMaxConnections int) CheckResult

PoolCapacityCheck verifies that the total configured pgxpool connections across all active services do not exceed postgres_max_connections.

Returns a CheckResult with:

  • Status "pass" when total_conns ≤ postgres_max_connections
  • Status "warn" when total_conns > postgres_max_connections (not a hard block)
  • Status "fail" when inputs are invalid (zero services, negative max)

This check fires in nself doctor --deep as PERF-POOL-01.

func PostgresChecks

func PostgresChecks(ctx context.Context, verbose bool) []CheckResult

PostgresChecks verifies pg_isready, replication lag, longest query, dead tuples, vacuum.

func SSLChecks

func SSLChecks(ctx context.Context, verbose bool) []CheckResult

SSLChecks verifies LE renewal cron, last renewal, OCSP stapling.

func SecurityChecks

func SecurityChecks(ctx context.Context, projectDir string) []CheckResult

SecurityChecks runs security diagnostics.

func SystemChecks

func SystemChecks(ctx context.Context, verbose bool) []CheckResult

SystemChecks runs system-level diagnostics: disk, memory, swap, load, clock sync, Docker, kernel.

type PoolConfig added in v1.0.11

type PoolConfig struct {
	ServiceName string
	MaxConns    int // per-service pgxpool MaxConns (0 = use pgxpoolDefaultMaxConns)
}

PoolConfig describes the pool sizing input for a single service. Each plugin or required service exposes one pool config.

func DefaultPoolConfigs added in v1.0.11

func DefaultPoolConfigs(services []string) []PoolConfig

DefaultPoolConfigs returns a pool config slice for the standard nSelf service set (4 required + up to 7 optional). Pass only the enabled services. Used by nself doctor --deep to compute the PERF-POOL-01 check.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL