Skip to content

feat: AI audit tools, OAuth wizards, doctor, and CLI UX polish - #2

Open
raymondproguy wants to merge 19 commits into
mainfrom
feature/ai-audit-tools
Open

feat: AI audit tools, OAuth wizards, doctor, and CLI UX polish#2
raymondproguy wants to merge 19 commits into
mainfrom
feature/ai-audit-tools

Conversation

@raymondproguy

Copy link
Copy Markdown
Contributor

Summary

Adds the AI-assisted admin tooling and OAuth/UX work accumulated on the branch:

New AI commands

  • csax ai config — interactive wizard: provider/model/API-key env var + auto-creates a genuinely read-only Postgres role (grants follow ai.AllowedEntities, handles Supabase pooler usernames, verifies the connection)
  • csax ai ask / csax audit ask — direct natural-language questions over any allowlisted entity
  • csax ai anomalies scan [--since 24h] — fixed-prompt audit scan for credential-stuffing/abuse patterns
  • PII redaction (redact.go): emails/IPs become stable placeholders (email_1, ip_2) before data is sent to the AI provider

New OAuth commands

  • csax oauth config — interactive BASE_URL/FRONTEND_URL + Google/GitHub credential wizard (prompts are explicit about BASE_URL being the backend URL)
  • csax oauth providers add <provider> — add one provider quickly
  • csax oauth users get <email> / csax oauth unlink <email> --provider <name> — direct-SQL admin management of linked identities
  • Adds FRONTEND_URL to config

New diagnostic command

  • csax doctor — one-shot DB/migration/AI/OAuth health check reusing the existing checks

UX

  • table.go: box-drawing table output with cell truncation and semantic audit-event coloring
  • spinner.go: terminal spinner during network/DB round trips
  • NO_COLOR support

Fixes

  • ai logs now forces audit-only entity and real rows (no aggregate), and warns when the question targeted another entity
  • Version reporting fixed: no more fake v0.1.0; target cryden bumped to v2.1.0

Docs: README updated for all new commands, the BASE_URL vs FRONTEND_URL distinction, and the AI privacy/redaction behavior.

Verified: go build ./... and go vet ./... pass on the final state.

Introduces withSpinner(), an async spinner shown around real network/DB round trips (AI provider calls, OAuth endpoint checks, read-only role verification). AI calls in particular took long enough during testing that a silent terminal looked hung rather than slow.

Only spins on a real terminal with colors enabled; in piped/scripted output a spinner is noise and would corrupt line-by-line parsing, matching the existing colorsEnabled gating. The line is cleared after fn returns so no spinner text is left behind regardless of outcome.
Adds printTable() in table.go, used to render columns/rows as a real box-drawing table with a header. Replaces the fixed-width printf layout in ai query and oauth providers list, which blew up row widths on long UUIDs/emails.

Cells are truncated with a trailing ellipsis past maxCellWidth instead of widening every row. When colors are disabled (piped output, NO_COLOR, non-TTY) it falls back to plain pipe-separated columns so scripts never see box-drawing characters mixed into captured data. Audit event types get semantic color: red for failed/reuse_detected/locked events, green for success/linked/unlocked.
Adds redactForSummary(), which replaces real email and IP values with stable per-result placeholders (email_1, ip_2, ...) before query results are handed to an AI provider for summarization.

Audit data contains real emails and IPs, and an operator may not want that leaving their infrastructure even to a provider they trust. Placeholders are stable within one result set so repetition and clustering (email_3 appears 5 times) stay visible to the model, but actual values never leave. The full unredacted data is still printed to the terminal afterward; only the Summarize input goes through this.
Adds csax ai config, an interactive wizard covering everything ai query/ai logs/ai audit need: AI_PROVIDER, AI_MODEL, the env var name holding the API key (the key itself is never stored), and a genuinely read-only Postgres role.

The read-only role was the biggest friction point in manual testing, so the wizard creates it directly via the admin DATABASE_URL rather than handing the user SQL to run by hand. Grants SELECT on exactly the tables ai.AllowedEntities covers, so the grants can never silently drift from what ExecuteQuery actually allows; also builds the matching READONLY_DATABASE_URL (with a Supabase pooler username quirk handled) and verifies it with a test connection. Role/password/identifier values are defensively escaped since Postgres protocol cannot parameterize DDL.

Also defines shared interactive helpers (promptDefault, promptYesNo, appendEnvValues) reused by the oauth config wizard; appendEnvValues updates .env in place (0600) instead of replacing the whole file like cmd_config.go does, preserving existing DATABASE_URL/JWT_SECRET.
Adds csax ai ask "<question>" (aliased as csax audit ask), a direct-question variant of ai query: it runs the same ExecuteQuery/SafeQueryStore path and answers in plain language instead of just printing a table.

Unlike ai logs this deliberately does NOT force the entity to audit_events — a real operator question ("anything weird with devray@example.com this week?") may legitimately need users or sessions data. The question text and redacted results are sent to the provider for the narrative answer; unsafe intents get the same ErrUnsafeQueryIntent treatment as ai query.
Adds csax ai anomalies scan [--since 24h], a fixed-prompt variant of ai logs: it runs the same auditOnlyProvider/ExecuteQuery path with a no-input prompt looking for repeated failed logins, token reuse detections, new/unusual login locations, or signup clusters.

Implemented as a thin wrapper over the ai logs code path rather than a separate implementation so the two commands cannot quietly drift apart in behavior. Results are redacted before summarization like ai logs, and the scan window is configurable via --since.
Adds csax oauth config, an interactive wizard that sets BASE_URL/FRONTEND_URL plus Google and GitHub client credentials in one pass, and prints the exact callback URLs to register in each provider console.

The prompts are deliberately explicit about BASE_URL vs FRONTEND_URL because that exact mixup broke a real deployment during testing: BASE_URL is the BACKEND public URL (where the /api/oauth/.../callback route lives), not the frontend domain. Adds the FRONTEND_URL field to csaxConfig so the wizard and future commands can read the real value.

Also adds printCallbackURLs(), which cmd_oauth providers add reuses, and capitalizeFirst() as a small formatting helper.
Adds csax oauth providers add <provider>, a narrow interactive path to configure one provider (client ID/secret + callback URLs) for someone who already has BASE_URL/FRONTEND_URL set — faster than rerunning the full oauth config wizard.

Also upgrades the existing commands: providers list now renders through printTable() with truncated cells instead of fixed-width printf (long UUIDs/emails no longer blow up the layout), and oauth test wraps the endpoint reachability check in withSpinner() so the network round trip does not look hung.
Adds csax oauth users get <email> [--json] (lists which providers an account has linked) and csax oauth unlink <email> --provider <name> (force-unlinks a provider, the admin escape hatch in the same spirit as users unlock).

oauth_identities has no engine store method exposed for these yet, so they query the known Postgres schema directly (SELECT by user_id / DELETE by user_id+provider) — the same direct-SQL pattern already used by users list, stats, and audit search, with no engine change needed. users get supports --json for scripting; unlink reports no-op cleanly when the account has no such provider linked.
… choice

Previously ParseQueryIntent overrode Entity to audit_events but left the Aggregate/GroupBy the model chose intact, so a vague prompt like "any failed logins recently" could come back as a single count row instead of the actual log entries ai logs exists to show.

Now the entity is forced to audit_events as before, and Aggregate/GroupBy are cleared so the command always returns real rows for review. It also records the original entity the model chose, so cmdAILogs can warn when the question was clearly about something else (e.g. "show me all users") instead of silently returning audit events that were never what the person asked for.
Wraps the ExecuteQuery and Summarize round trips in withSpinner() so the audit search does not look hung during provider calls, and sends redactForSummary() output (placeholder email_1/ip_2 values) to the provider instead of raw rows, with an updated system prompt telling the model to treat placeholders exactly as given.

Replaces the old formatRowsForSummary helper, which built the same text but without redaction.
Wraps the provider Summarize call in cmdAIAudit with withSpinner() so the fixed-checklist narrative generation does not look hung — same network-round-trip coverage as the other ai commands.
Wraps the ExecuteQuery call in withSpinner() so query intent/planning does not look hung during provider calls, and renders result rows through printTable() instead of the old fixed-width printf loop — long UUIDs and emails are truncated with an ellipsis instead of widening every row.
Previously colors were decided purely by terminal detection, so a TTY with NO_COLOR set (https://no-color.org) still got escape codes. That breaks scripts that explicitly set NO_COLOR to force plain output, and the project now documents NO_COLOR support.

colorsEnabled is now isTerminal() && NO_COLOR unset — any non-empty NO_COLOR disables color regardless of terminal detection, since it is an explicit preference overriding csax’s own guess. All existing color gating (table output, spinner, status colors) inherits this automatically.
csaxVersion previously claimed v0.1.0, a version that was never actually tagged, so csax version / health lied about what was running. It is now "dev" until a real release tag is cut.

targetCrydenVersion is bumped from v2.0.0 to v2.1.0 to match the cryden/v2 module version actually used in go.mod, so the health check no longer warns about a mismatch against the dependency in use.
Adds csax doctor, a one-shot health check covering the database (ping + migration table presence), AI-assisted command configuration (the same runAuditChecklist findings health-related ai audit uses, plus provider setup), and an OAuth configuration summary.

It reuses the existing check functions (db ping, runAuditChecklist, oauthProviders, newLLMProvider) rather than re-implementing them, so there is one source of truth for each check instead of two that could drift. checkMigration() reports missing tables with a concrete fix hint (run csax migrate up, or apply the 0002_oauth_identities migration).
Adds CLI dispatch and usage text for the new commands: ai config, ai anomalies scan, ai ask, audit ask (alias), oauth config, oauth providers add, oauth users get, oauth unlink, and doctor. The oauth providers subcommand is restructured to a list|add switch, and usage() lists every command with the interactive setup hint.

Also prints a small ASCII logo when the binary runs with no arguments on a colored terminal, and the bare usage line for audit now mentions ask. The audit ask alias is handled inside the audit switch (Go forbids duplicate switch cases, so it cannot be a separate top-level case).
Updates the README for the new surface area: audit ask, oauth providers add / users get / unlink / config, ai anomalies scan / ask / config, and doctor. Documents the BASE_URL vs FRONTEND_URL distinction (a real mixup that broke a deployment during testing), the FRONTEND_URL env var, and the interactive config wizards.

Adds a privacy note that ai ask/ai logs/ai anomalies scan redact emails and IPs to stable placeholders before anything is sent to the AI provider, while the full unredacted data is still printed to the terminal. Design notes now cover table.go/spinner.go output behavior, doctor reusing existing checks, the --json coverage gap, and the flat-package-main structural gap that was deliberately deferred.
Adds the missing newline at EOF to .gitignore (git was warning about it) and removes the stray trailing blank line in the 0002_oauth_identities migration. Pure whitespace hygiene, no content change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant