All notable changes to Snout are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Console v2 design pass (EPIC-CONSOLE-V2). The dashboard and extension popup now share one
design-token source (
tokens.css,--sn-*, WCAG-checked:#8c909fdemoted to borders-only, unknown-verdict color moved to an AA-passing#9aa3bd). A de-robotization pass makes all chrome Inter sentence case with JetBrains Mono reserved for data — verdict/governance/band pills keep mono caps with glyph + label + color (never color alone) — and provenance is quiet text. The Command Center gains a hero metric, a vendors×controls coverage matrix (with a live weakest-control note), a needs-attention queue, and a live system panel; the sidebar gains a portfolio pulse bar and an engine feed of real events (assessments, KB verifies, discovery) plus a measured once-a-minute heartbeat — no fabricated telemetry. An accessible toast system replaces every nativealert()(auto-dismiss with pausable progress,aria-live, ESC, reduced-motion), status banners follow the v2 recipes (including a new reduced-grounding banner), the radar gets verdict-colored dots + weight labels, and the new brand mark/wordmark, favicons, and OG metadata land across web + extension (which also bundles its fonts locally and is renamed "Snout — Shadow SaaS & Auth Discovery"). A top-level React error boundary replaces white-screens with a friendly, reload-able fallback; a Vitest + Testing Library smoke suite covers the dashboard shell, the readiness/setup states, demo mode, and the boundary (the web tier had no tests before). Repo-wide ESLint (flat config — typescript-eslint for the server, react-hooks for the web, webextension globals for the extension) runs in CI alongside the new web test job, and CI pins Node 20.19. - First-run activation (EPIC-ACTIVATION). The dashboard now reflects honest readiness: an
/api/configreadinessblock + a/healthassessReadyflag drive a real status badge (Connecting… / Backend offline / Setup needed / Reduced grounding / System Healthy) instead of a hardcoded "System Healthy", with a setup banner naming the exact env var to set.POST /api/assessnow fails fast with a clearprovider_not_configuredmessage (no doomed LLM call) when no key is configured. A runtime Load sample data demo mode — also reachable via a shareable?demo=1link — lets anyone explore the full dashboard offline with no key (Run assessment synthesizes locally; an Exit-demo control returns to live state). Readiness exposes booleans only, never secret values. - Verified core + KB verification meter (EPIC-MOAT Sprint 2).
npm run kb:statsplus a dashboard verified-% bar (queue prioritized most-unverified-first, per-vendor bulk verify) make "human-verified and compounding" measurable; the eval adds a KB-verified-only comparison row so verification's effect on accuracy is visible. The top-tier vendors (Slack, GitHub, Salesforce) are hand-authored tohuman-verified against first-party docs (with specific citations), and the seeder skips held-out benchmark vendors so it can't inflate the eval. - Bias-resistant eval (EPIC-MOAT Sprint 1). The eval breaks the label↔KB circularity:
benchmark cases are tagged
inKb, the report leads with held-out (never-in-KB) accuracy, and a--baselinemode compares a naive floor, KB-only, and (with--live) the model with and without the KB — reporting the KB lift. Independent held-out vendors (Box, Zendesk, Miro, 1Password) were added; the label-independence protocol is documented inserver/eval/README.md. - Postgres store + per-tenant data isolation (
server/src/store.pg.ts,pg). SetDATABASE_URLto switch from the zero-config JSON store (single-tenant) to a Postgres backend that scopes every row by tenant — each query carriesWHERE tenant = $1, so one tenant can never read or write another's data (closing the prior "looks multi-tenant, isn't" gap whereTENANT_IDwas only an audit tag). AwithTenantmiddleware carries the request tenant viaAsyncLocalStorageso the store facade scopes calls without threading a parameter everywhere; the JSON store is unchanged and remains the default. Schema auto-creates on first use;server/db/schema.sqlships as reference (+ optional row-level-security notes). - OIDC dashboard login (
server/src/oidc.ts,server/src/routes/auth.ts,openid-client+jose). When fully configured (OIDC_ISSUER+ client id/secret + redirect URI +SESSION_SECRET), users sign in via your IdP (Authorization Code + PKCE, state, nonce) and a signed, httpOnly session cookie authorizes API calls. Bearer tokens still work alongside it. Only ID-token claims are used for identity — no IdP tokens are stored. Role maps from a configurable claim (OIDC_ADMIN_VALUE); the session tenant is authoritative (a user can't escape it viax-tenant). The dashboard shows a sign-in gate and a sign-out control. - Google Workspace IdP poller (
server/src/pollers.ts,jose). Completes the poller trio: signs a service-account JWT (RS256) impersonating an admin via domain-wide delegation, exchanges it for a read-only Reports scope token, and pulls login + OAuth-grant activity from the Admin SDK Reports API into the existinggoogleadapter. Off unless the SA credentials + admin subject are set; outbound only to Google's fixed hosts; the key is never logged. - IdP pull-pollers (
server/src/pollers.ts, depth D4). Optional zero-touch discovery: Snout periodically pulls Okta System Log (OKTA_LOG_URL+ SSWSOKTA_API_TOKEN) and Microsoft Entra sign-ins (ENTRA_TENANT_ID/ENTRA_CLIENT_ID/ENTRA_CLIENT_SECRET) via native fetch and feeds them through the same adapters as the push webhooks. Off unlessIDP_POLL_INTERVAL_MINUTESis set; outbound only to your IdP;OKTA_LOG_URLis validated at startup; credentials are never logged. - Supporting depth (D4/D5). Multi-sensor discovery now reduces hosts to the registrable
domain (eTLD+1, heuristic public-suffix set — no new dependency) so sensors dedupe correctly.
Optional scheduled re-assessment (
REASSESS_INTERVAL_HOURS) re-runs stale assessments and fires regression alerts, making monitoring continuous. OAuth scope risk in posture is now tiered (write/admin = high, broad read = medium, offline = low). - KB compounding loop (
server/scripts/seed-kb.ts,GET /api/kb, dashboard Knowledge view).npm run seed:kbbatch-runs the agent over a vendor list to generate unverified proposals at scale; the Knowledge view is a verification queue where a human promotes proposals to verified and sees freshness (facts older than 180 days flagged stale). - Assessment-correctness passes (
server/src/verify.ts,server/src/citations.ts), both off by default.VERIFY_FINDINGS=trueruns an adversarial refutation pass that deterministically demotes verdicts it can't defend tounknown(never demoting human-verified KB facts, never upgrading).CHECK_CITATIONS=truefetches cited pages — SSRF-guarded (safeUrl()+redirect: manual+ timeout + size cap) — and drops citations that don't support the control (unfetchable pages are kept). - Auth-posture findings (
server/src/posture.ts). Each discovered app is scored for identity risk (no corporate SSO, local-password login, consumer IdP, broad/long-lived OAuth scopes) with a risk score + badges in the Discovered view;GET /api/catalog/exportemits a flat posture feed for SIEM/BI. Findings only — not inline enforcement. - Continuous monitoring (
server/src/routes/alerts.ts). APOST /webhooks/breachHMAC feed ingests SaaS breach/CVE items, and re-assessments raise an alert on any control regression; alerts surface in the dashboard and atGET /api/alerts. - RBAC + audit log + tenant tag (
server/src/security/auth.ts). The adminAPI_TOKENcan mutate; an optional read-onlyAPI_VIEWER_TOKENis GET-only (writeGuard). Every mutating call is recorded to an append-only audit log (GET /api/audit, admin only) tagged with role andTENANT_ID. Per-tenant data isolation remains a Postgres-Store concern. - Open IPSIE-control knowledge base + measured accuracy (
server/src/kb.ts,kb/,server/eval/). Per-vendor control facts live in community-contributablekb/<domain>.jsonfiles (validated bynpm run kb:validate) plus runtime human verify/override viaPOST /api/kb/:key/:control. The agent now reads human-verified facts first as trusted structured priors, researches only the gaps, and returns a per-control confidence; verified facts are authoritative and reused across assessments. Each control carries provenance (kb-verified·agent·kb-proposed), surfaced in the dashboard with an inline verify control; agent findings are stored as unverified proposals so the KB compounds. A labeled benchmark + eval harness (npm run eval,kb/EVAL.md) measures accuracy deterministically (KB-only) with an opt-in--livemode — accuracy is measured, not asserted. - Multi-sensor discovery (
server/src/discovery.ts). Inventory apps + auth methods without the browser extension by forwarding telemetry to two new HMAC-signed webhooks:POST /webhooks/idp/:source(okta|entra|googlesign-in/audit logs) andPOST /webhooks/email(forwarded signup/account email metadata). All sensors — extension, IdP logs, email — merge by domain into one discovered record, now with a capped per-app history (events[]) shown in the Discovered view. Push-only: no IdP/mailbox credentials are stored and no outbound calls are made to ingest. Events without a resolvable app domain are skipped and counted. Gated byENABLE_CATALOG; reusesSNOUT_WEBHOOK_SECRET. - Configurable LLM provider (
server/src/llm/).LLM_PROVIDER=anthropic(default) keeps the Anthropic Messages API withweb_search;ANTHROPIC_BASE_URLlets it run through a proxy / API gateway / internal Anthropic-compatible endpoint.LLM_PROVIDER=openaitargets any OpenAI-compatible/v1/chat/completionsendpoint viaLLM_BASE_URL,LLM_API_KEY,LLM_MODEL(OpenAI, LiteLLM, OpenRouter, vLLM, Ollama). Existing setups are unchanged —ANTHROPIC_API_KEYalone behaves exactly as before./healthand/api/confignow report the effective provider/model.
- Dependency majors, validated. Upgraded express 4 → 5 (+
@types/express5), TypeScript 5 → 6 (with tsconfigignoreDeprecations: "6.0"for the deprecatednode10module resolution), and Tailwind CSS 3 → 4 (PostCSS plugin moved to@tailwindcss/postcss,@tailwinddirectives →@import "tailwindcss", a v3 border-color compatibility shim, JS config dropped in favor of v4 auto-detection). Each was validated against the full test suite + build before merge; no behavior change. - Smaller initial web chunks. The production build now vendor-splits
recharts(~273 kB) and React (~179 kB) into their own cacheable chunks via RolldownadvancedChunks, so no single chunk exceeds Vite's 500 kB advisory (app chunk is ~70 kB). The single-file demo build is unchanged (one inlinedindex.html). - Repositioned around IPSIE. README and dashboard copy now frame the product as open, transparent, IPSIE-aligned identity-trust scoring — the six controls are mapped to the OpenID Foundation's IPSIE (Interoperability Profile for Secure Identity in the Enterprise) control areas. Wording only; the controls, weights, and scoring are unchanged.
- Upgraded core dependencies to current majors: zod 3 → 4 (server;
z.recordnow takes an explicit key schema) and React 18 → 19 (web). No behavior changes.
- Added METHODOLOGY.md — the public, reproducible specification of the transparent-mean score, verdict weights, IPSIE-aligned control mapping, readiness bands, and grounding modes.
- Added a Scope — what Snout is, and isn't section to the README stating the product boundary: Snout sees and reports identity-control posture and complements your ITDR/SSPM and IdP; it is not an inline enforcer.
- Rebranded the product from "Trust Agent" to Snout. Renamed packages, the
extension, the
/snoutSlack/Teams command, theSNOUT_WEBHOOK_SECRETenv var, and the extensionsnoutUrl/snoutTokensettings. No functional changes.
- Providers without live web search run with reduced grounding: Snout deterministically
drops citations and downgrades unproven
supported/partialverdicts tounknown(recommendation capped atHold), recorded as each assessment'sgroundingmode.validateAgentOutput()runs on every provider's output and cannot be bypassed. - Operator base URLs (
ANTHROPIC_BASE_URL/LLM_BASE_URL) are trusted config validated for scheme + credentials (safeBaseUrl()) and fail closed at startup;safeUrl()stays strict (private-host block intact) for untrusted user/citation URLs. Provider error bodies are logged server-side only and never returned to clients; keys/base URLs are never logged.
1.0.0 - 2026-06-20
- Assessment agent — researches any SaaS app against the six critical enterprise SaaS controls (SSO, lifecycle, entitlements, risk signals, logout, token revocation), with live web citations, a transparent trust score, and a RAPID-replacing governance packet (recommendation, conditions, residual risks, stakeholder ownership).
- Command Center dashboard (Obsidian Command immersive UI) — portfolio KPIs, recent assessments, a needs-attention feed, searchable assessment catalog, and a detail view with the trust hexagon, per-control evidence, and operational due diligence.
- Integrations — inbound catalog webhooks for ServiceNow / Okta / NetSuite (HMAC
signed), a Slack
/snoutslash command, a Microsoft Teams bot, and deployable handler snippets in-app. - Shadow discovery —
/api/catalogbulk ingest, a Discovered dashboard view, and one-click assess-and-link from a discovered app. - Browser extension (Manifest V3) — captures shadow SaaS and shadow auth (corporate SSO vs social/consumer IdP vs local password vs OAuth grants) locally, with one-click Assess, bulk Sync, Chrome Enterprise managed configuration (zero-touch), and optional background auto-sync.
ENABLE_CATALOGcapability flag — ship the product with or without shadow discovery from one build (hides the Discovered view and unmounts the catalog routes).
- Prompt-injection defenses (OWASP LLM01): untrusted input is fenced and labelled as
data, a hardened system prompt refuses embedded instructions, and the model's JSON is
deterministically validated — verdicts coerced to a known enum, lengths/arrays clamped,
and citation URLs allow-listed. Least-privilege read-only
web_search. - Fail-closed bearer auth (required in production), per-client and per-flow rate limits, a concurrency cap on the assess flow, SSRF URL allow-listing, helmet + CSP + strict CORS, a leak-free error handler with request ids, and per-assessment audit logging. Full threat model and control mapping in SECURITY.md.