Knowledge Base Article

MSP Shadow AI Governance Console

Real-time detection of unauthorized local AI models — Ollama, LM Studio, llama.cpp, Whisper — rogue GPU usage, and unapproved AI tooling across all client endpoints. Powered by SentinelOne Deep Visibility v2.1 with per-site tenant isolation and six wired remediation actions routed through your backend proxy.

SentinelOne DV v2.1 6 Actions Wired 8 Preset Queries Multi-Tenant MSP Proxy Architecture Defender Fallback
01

What Is This Console

Purpose, scope, threat model

The Shadow AI Governance Console is an MSP-facing security tool that detects unauthorized local AI model deployments on client endpoints. It surfaces processes, file paths, and network ports associated with Ollama, LM Studio, llama.cpp, Whisper, and other local AI runtimes — identifying which clients have active models, which endpoints are affected, and what data exposure risk those models create.

The console is built around the SentinelOne Deep Visibility API. Every detection, endpoint event, and process match is sourced from a real-time DV query sent to your S1 management instance via a backend proxy. Client data and event history rendered in the UI are populated from that query pipeline — not from independent EDR agents or a separate SIEM.

Why Shadow AI Is a Security Problem

Local AI models running on employee workstations create several distinct risks: sensitive data (source code, financial records, PHI) is fed into models with no audit trail; Ollama's default bind to 0.0.0.0:11434 exposes the model API on the network; .gguf model files may be sourced from untrusted registries; and GPU abuse on shared or OT network segments can mask malicious compute usage. Standard EDR rules do not flag these behaviors by default — Deep Visibility queries are required.

What the Console Is Not

This is not a standalone EDR or a SIEM. It does not generate its own telemetry — it queries SentinelOne's existing telemetry pipeline. It requires Deep Visibility to be licensed and enabled at the Site level for each client. For clients using Microsoft Defender (the Mu Manufacturing case in the demo data), the console displays a fallback indicator but does not actively query Microsoft Graph Advanced Hunting — that integration must be built separately in your proxy.

02

Intended Audience

Who uses it and how

Primary
MSP Security Analyst
Runs portfolio-wide scans, reviews detection events, executes isolation and port-block remediation actions for high-risk clients.
Secondary
vCISO / Compliance Lead
Reviews per-client AI governance policy status, user acknowledgment rates, and incident ticket state for audit evidence.
Tertiary
MSP Engineer
Uses the Query Builder to build and validate custom DV queries, preview API payloads, and copy queries for use in S1 Automation Rules.
03

Layout & Sections

Page structure, navigation, responsive breakpoints

The page is a single scrollable document with a sticky topbar, a fixed right-side dot navigation, an API status banner, a KPI strip, and five distinct content sections. Navigation between sections happens by scrolling, clicking topbar nav links, or clicking the right-side dot nav — all use smooth scroll to the section's anchor ID.

Topbar50px sticky header with brand, section nav links, a live dot, proxy connection label, and a "SHADOW AI ACTIVE" threat badge. Backdropped with blur. Bottom line is a multi-color gradient (red → orange → cyan → purple).
Scan OverlayFull-screen modal that appears during runScan(). Shows dual spinning rings and step text as the proxy makes S1 API calls. Dismisses automatically when the final DV events response is received.
API Status BannerHorizontal scrollable strip showing connection status for: S1 Proxy, Auth, Region, Sites, Agents, DV Query init, DV Status poll, DV Events fetch, and Defender fallback. Each node shows a dot (live/mock/error), current value, and the specific endpoint path.
KPI StripSeven tiles: Total Detections, Critical Clients, Shadow AI Models, Endpoints Scanned, .gguf Files Found, GPU Anomaly Flags, Clean Sites. Computed from the client array on every render.
Filter BarSticky filter strip with Risk, EDR, Model dropdowns and a client search field. All filters apply client-side against the in-memory client array. The "Run Deep Visibility Scan" button triggers the full proxy scan cycle.
Detection SectionClient card grid sourced from S1 Sites. Each card shows EDR type, siteId, detected models, event count, agent coverage, .gguf count, GPU flag, and port 11434 status. Click opens the Deep Dive panel inline below the grid.
Analytics SectionFive Chart.js charts: Detections by Model Type (doughnut), Detection Events 30 Days (line), Risk Score by Client (bar), EDR Coverage (horizontal bar), GPU Usage Anomalies 14 Days (multi-line). All render from the static client array in demo mode; wire to DV events data in production.
Query BuilderPreset selector, DSL query textarea, site selector, date range, limit, and Execute/Preview buttons. Wired to POST real queries to your proxy. Preview mode shows the JSON payload without executing.
Policy StatusPer-client table showing written AI policy, S1 behavioral rule deployment, user acknowledgment, open incident, and event count. Derived from client risk metadata — connect to your GRC/PSA for live status.
Alert FeedChronological list of detection alerts with severity, client, message, endpoint detail, and source metadata. In demo mode, populated from the static alertsData array. In production, populate from DV events grouped by client.

Responsive Breakpoints

ViewportLayout Change
> 900pxFull layout — right sidenav visible, full padding, 2-column analytics/deep-dive grids
< 900pxSidenav hidden, padding collapsed to 20px, all 2-column grids collapse to 1 column, hero font size reduced
04

API Audit Findings

Complete audit against official SentinelOne API v2.1 documentation

Audit Complete — All Issues Resolved

The console was audited against official SentinelOne API v2.1 documentation. Six issues were identified and all have been corrected in the shipped version. The console is proxy-ready — deploy your aggregator at the paths in Section 20 and it goes live with no further frontend changes.

Finding 1 — runScan Was Pure Theatre (Fixed ✓)

The original runScan() used a setTimeout loop to animate progress text with hardcoded step descriptions. It made zero actual API calls — the "69 total events" message was fabricated. The overlay gave the visual impression of real scanning while doing nothing. This has been replaced with a real async fetch chain that calls your proxy at five sequential endpoints: sites, agents, dv/init-query, dv/query-status (polled until FINISHED), and dv/events. Falls back gracefully if the proxy isn't deployed yet.

Finding 2 — runQuery Was a Fake setTimeout (Fixed ✓)

The original runQuery() waited 1.2 seconds then displayed a fabricated response with a random queryId, hardcoded 342ms latency, and a random estimatedResults count. No real query was sent. The function is now an async fetch that POSTs to /api/shadow-ai/edr/sentinelone/dv/init-query, extracts the real queryId from the response, and displays the actual server response in the terminal. When the proxy isn't deployed, it shows the ready-to-send payload as reference rather than a fake result.

Finding 3 — All 6 Remediation Actions Were Toast-Only (Fixed ✓)

Every remediation action in the Deep Dive panel called only toast('Action queued: ...') with a 2-second color reset — no API call of any kind. All six actions are now routed through ACTION_ROUTES to specific proxy endpoints. See Section 12 for the full routing table and per-action API constraints.

Finding 4 — Invalid DSL Field: GPUUsage (Fixed ✓)

Critical — This Query Would Return Zero Results

GPUUsage is not a valid SentinelOne Deep Visibility DSL field. The DV query language exposes process, file, network, and registry telemetry fields — GPU utilization metrics are not part of the DV schema. A query using GPUUsage > 60 would silently return no results without an error, making it appear that no GPU anomalies exist. The preset has been replaced with a valid process-correlation query that identifies AI-related processes known to be GPU-intensive (Python with cuda/torch args, ollama, llama-server).

Finding 5 — Missing dv/query-status Poll Step (Fixed ✓)

The S1 Deep Visibility API is asynchronous — POST /web/api/v2.1/dv/init-query returns a queryId and status RUNNING, not results. You must then poll GET /web/api/v2.1/dv/query-status?queryId={id} until the status is FINISHED, then fetch results with GET /web/api/v2.1/dv/events?queryId={id}. The original scan flow skipped the status poll entirely. The API banner also did not show the dv/query-status or dv/events endpoints as distinct steps. Both have been added.

Finding 6 — Fake Live Simulation Interval (Fixed ✓)

A setInterval running every 6 seconds randomly incremented detection counts on clients that had detections, then called renderKPIs(). This had no connection to real data — it fabricated upward-trending metrics to simulate live activity. It has been removed. Detection count updates now only happen when a real scan completes.

Audit Summary

FindingSeverityStatus
runScan() — setTimeout theatre, zero API callsCritical✓ Fixed — real 5-step fetch chain
runQuery() — fake 342ms response, random queryIdCritical✓ Fixed — real async fetch + real queryId
6 remediation actions — toast() only, no fetchCritical✓ Fixed — ACTION_ROUTES with real endpoints
GPUUsage DSL field — not a valid S1 DV fieldCritical✓ Fixed — replaced with valid process query
dv/query-status poll step missing from scan flowHigh✓ Fixed — async poll loop added
Fake 6s detection increment intervalHigh✓ Fixed — interval removed
S1 API endpoints — all paths correct per v2.1 docsPass✓ No change needed
Auth scheme — ApiToken header correct per docsPass✓ No change needed
DV DSL operators — ContainsCIS, EndWith, IN, = all validPass✓ No change needed (excluding GPUUsage)
05

Proxy Architecture

Why a proxy is required and how it fits

SentinelOne's API requires an Authorization: ApiToken {token} header on every request. That token cannot live in browser-side JavaScript — it would be exposed to any user who opens DevTools. All S1 API calls are therefore routed through a backend proxy that holds the token server-side, validates dashboard requests, and forwards them to your S1 management instance.

Architecture Flow

Dashboard (browser) → POST /api/shadow-ai/edr/sentinelone/dv/init-query → Your Proxy (server) → POST https://usea1-api.sentinelone.net/web/api/v2.1/dv/init-query with Authorization: ApiToken {vault_token} → S1 Management API → Response back through proxy to dashboard.

The dashboard sends X-Dashboard: shadow-ai-gov on every request. Your proxy should validate this header as a lightweight shared-secret check before forwarding. For production deployments, add proper auth (JWT, session token, or mTLS) between the dashboard and proxy.

Multi-Tenant Site Isolation

Each client in the console maps to a SentinelOne Site — a tenant-isolated container within your S1 account. The siteId field (e.g. site_acme) is the S1 Site ID for that client. All DV queries use siteIds: [clientSiteId] to restrict results to that client's endpoints. Your proxy must map your internal client identifiers to real S1 Site IDs obtained from GET /web/api/v2.1/sites.

Token Scope

The S1 Service User token used by your proxy should be Account-scoped with Viewer role for read operations (sites, agents, DV queries). For remediation actions (disconnect, firewall-control), the Service User requires elevated permissions — either a separate token with the appropriate role, or a custom role with exactly the permissions listed in Section 07.

06

Auth — ApiToken

SentinelOne Service User token setup and scope requirements

SentinelOne uses Authorization: ApiToken {token} for all API requests. The token is generated from a Service User — not a console user account — and is scoped to Account or Site level. For an MSP, Account-level scope allows a single token to query all Sites within the account.

  1. 01
    Create a Service User
    In the S1 Management Console: Settings → Users → Service Users → Actions → Create New Service User. Set an expiration date — SentinelOne recommends rotating tokens monthly or less. For MSP use, scope to Account level.
  2. 02
    Assign Role
    For read-only DV operations (sites, agents, dv/init-query, dv/events): built-in Viewer role is sufficient. For remediation actions (disconnect, firewall-control): create a custom role with Endpoints → Disconnect from Network + Reconnect to Network + Firewall Control. Deep Visibility requires the Deep Visibility feature to be enabled in the Site policy.
  3. 03
    Copy the Token Once
    The API token is shown only at creation time. Copy it immediately and store it in your vault (HashiCorp Vault, Azure Key Vault, AWS Secrets Manager, or your proxy's environment variables). It cannot be retrieved again — you must delete and recreate the Service User to get a new token.
  4. 04
    Configure Your Proxy
    Set the token as an environment variable in your proxy runtime (S1_API_TOKEN). The proxy reads it at startup and injects it as the Authorization header on every forwarded request. Never hardcode the token in source code or pass it to the browser.
Authorization header format — confirmed against S1 API v2.1 docs
// Every S1 API request from your proxy must include:
Authorization: ApiToken {your_service_user_token}
Content-Type: application/json

// Example proxy injection (Node.js):
const headers = {
  'Authorization': `ApiToken ${process.env.S1_API_TOKEN}`,
  'Content-Type': 'application/json'
};
07

S1 Endpoint Reference

All SentinelOne API v2.1 endpoints used by this console — verified against official documentation

Method + PathPurposeRequired Role / PermissionKey Params
GET /web/api/v2.1/sitesFetch all S1 Sites (clients) in the accountViewerlimit, state=active
GET /web/api/v2.1/agentsAgent inventory — count online/offline per siteViewersiteIds, isActive, limit
POST /web/api/v2.1/dv/init-queryInitialize a Deep Visibility query — returns queryIdViewer + Deep Visibility licensequery, fromDate, toDate, siteIds, limit
GET /web/api/v2.1/dv/query-statusPoll query completion — returns RUNNING or FINISHEDViewer + Deep Visibility licensequeryId (query param)
GET /web/api/v2.1/dv/eventsFetch query results — returns matching eventsViewer + Deep Visibility licensequeryId, limit, sortBy
POST /web/api/v2.1/agents/actions/disconnectNetwork-isolate endpoint(s) from the networkCustom role: Endpoints → Disconnect from Networkfilter.ids or filter.siteIds
POST /web/api/v2.1/agents/actions/connectRe-connect isolated endpoint(s) to the networkCustom role: Endpoints → Reconnect to Networkfilter.ids
POST /web/api/v2.1/firewall-controlCreate a firewall rule to block a port or IPAdmin or custom Firewall Control roledata.name, data.rules[], data.siteIds
POST /web/api/v2.1/threats/mitigate/killKill a threat-tagged processSOC or IR Team rolefilter.ids (threatIds — NOT processIds)
POST /web/api/v2.1/threats/mitigate/remediateDelete threat-tagged files and revert changesSOC or IR Team rolefilter.ids (threatIds only)
Two Actions Have No Direct S1 API Path

"Kill ollama/lm-studio processes remotely" and "Delete .gguf model files" cannot be performed via a direct S1 API call targeted by process name or file path. The S1 mitigation endpoints (threats/mitigate/kill, threats/mitigate/remediate) operate on threat-tagged objects that have an S1 threat ID — they cannot target arbitrary running processes or arbitrary file paths. The correct approach for these two actions is to use the S1 Remote Script Library: upload a PowerShell or Bash script to S1, then invoke it via POST /web/api/v2.1/remote-scripts/execute targeting the affected endpoint by agent ID.

Deep Visibility Query Flow — Step by Step

  1. 01
    POST dv/init-query
    Send query DSL string, date range, siteIds, and limit. Response: {data: {queryId: "abc123", status: "RUNNING"}}. Store the queryId.
  2. 02
    Poll GET dv/query-status?queryId={id}
    Response returns status: "RUNNING" or status: "FINISHED". Poll every 2 seconds, max ~12 times. Most queries finish in 3–10 seconds. If not finished after 24s, the query may still be running — increase poll count or increase poll interval.
  3. 03
    GET dv/events?queryId={id}
    Once status is FINISHED, fetch the results. Response: {data: [{processName, filePath, networkPort, computerName, ...}], pagination: {totalItems, nextCursor}}. Paginate using cursor if totalItems exceeds your limit.
Complete DV query flow — POST body for shadow AI scan
// Step 1 — POST /web/api/v2.1/dv/init-query
{
  "siteIds": ["YOUR_SITE_ID"],
  "query": "ProcessName ContainsCIS \"ollama\" OR ProcessName ContainsCIS \"LM Studio\" OR ProcessName ContainsCIS \"llama-server\" OR ProcessName ContainsCIS \"whisper\" OR FilePath EndWith \".gguf\" OR NetworkPort IN (\"11434\",\"1234\",\"8080\")",
  "fromDate": "2026-02-01T00:00:00Z",
  "toDate":   "2026-03-22T23:59:59Z",
  "limit":    500
}

// Step 2 — GET /web/api/v2.1/dv/query-status?queryId=abc123
// Poll until: { "data": { "status": "FINISHED" } }

// Step 3 — GET /web/api/v2.1/dv/events?queryId=abc123&limit=500
// Returns matched events with full process and file telemetry
08

API Status Banner

Connection nodes, dot colors, and what each displays

The API status banner at the top of the page shows nine nodes rendered by renderApiBanner(). Each node shows a dot (green = live, yellow = mock/pending, red = error), a current value, and the specific API endpoint it represents. In the shipped version these are static — wire your proxy health-check responses into this function to show real-time connection status.

NodeEndpointLive Dot When
S1 Proxy/api/shadow-ai/edr/sentineloneProxy health check returns 200
AuthAuthorization: ApiToken {SERVICE_USER}Never live — credential display only, always mock dot
Regionusea1-api.sentinelone.netSites endpoint reachable
SitesGET /web/api/v2.1/sitesSites data returned successfully
AgentsGET /web/api/v2.1/agentsAgents data returned successfully
DV QueryPOST /web/api/v2.1/dv/init-queryMock until a scan is actively running
DV StatusGET /web/api/v2.1/dv/query-statusMock until a scan is actively polling
DV EventsGET /web/api/v2.1/dv/eventsMock until events have been fetched
DefenderMicrosoft Graph Advanced HuntingAlways yellow fallback — not yet integrated
09

KPI Strip

Seven tiles — what each counts and where the data comes from

TileComputed AsColor
Total DetectionsSum of client.detections across all clientsRed (c-red)
Critical ClientsCount of clients where risk === 'critical'Red (c-red)
Shadow AI ModelsStatic "12 unique" — update from DV events distinct model namesOrange (c-orange)
Endpoints ScannedSum of client.agents across all clientsCyan (c-blue)
.gguf Files FoundSum of client.ggufFiles across all clientsPurple (c-purple)
GPU Anomaly FlagsCount of clients where gpuFlag === trueOrange (c-orange)
Clean SitesCount of clients where risk === 'low'Green (c-green)

KPI tiles have a 2px top color bar, lift-and-shadow on hover, and cursor:default — they are display-only and do not open drawers or drill-down panels. In production, renderKPIs() is called after each completed scan cycle rather than on a timer.

10

Detection Grid

Client cards, risk levels, model tags, and stat cells

The client grid renders one card per entry in the clients array, filtered by the active filter bar state. Each card is clickable and toggles the Deep Dive panel inline below the grid. Cards are colored by risk level — a 2px top border in red (critical), orange (high), yellow (medium), or green (low).

Model TagsPurple tags showing detected model names (e.g. "Ollama 0.6.2", "llama3:8b"). Sourced from the client.models array. In production, populate from distinct ProcessName values returned by the DV query for that site.
Events statCount of DV detection events for this client. Red if >10, yellow if >0, green if 0.
Agents statOnline/total agent count from agentsOnline/agents. Source from GET /web/api/v2.1/agents?siteIds={siteId}&isActive=true.
.gguf statCount of .gguf model files found by the FilePath query. Purple if >0, green if 0.
GPU FlagBoolean — orange "YES" if the process-correlation GPU query returned matches for this client, else green dash.
Port 11434Boolean — red "OPEN" if NetworkPort = "11434" was found (Ollama API actively bound), else green dash. This is the most dangerous indicator as it means the Ollama API is network-accessible.
EDR BadgePurple "● SentinelOne · {siteId}" for S1-managed clients. Yellow "● Defender · Fallback EDR" for non-S1 clients. The Defender path does not have active DV querying — see Section 19.
11

Deep Dive Panel

Per-client event log, endpoint list, API payload preview, governance status

Clicking a client card opens the Deep Dive panel inline below the grid. The panel is a 2-column grid with four cards: Event Log, Affected Endpoints, API Call preview (terminal), and Governance Policy Status. Clicking the same card again or pressing the × button collapses it.

Event LogChronological list of detected AI process events for this client. Each row shows timestamp, process name, detail string, and severity badge. Sourced from client.events[] — in production, populate from DV events filtered to this site's agentId values.
Affected EndpointsList of endpoint hostnames where AI processes were detected, with username, PID, port, and process name. Sourced from client.endpoints[]. The FLAGGED status badge is static — in production, derive status from most recent DV event timestamp.
API Call TerminalSyntax-highlighted JSON showing the exact DV query payload that would be sent for this specific client — correct siteId, query string, date range. This is always accurate. The terminal label shows the real endpoint path POST /web/api/v2.1/dv/init-query · siteId: {id}.
Governance Policy StatusSix policy items derived from client risk metadata: Written AI Policy, S1 Behavioral Rule, User Acknowledgment, Incident Ticket, DLP Policy, Last Audit. Currently computed from risk level — in production, source from your GRC system or PSA custom fields.
12

Remediation Actions

Six wired actions — API routing, S1 constraints, and what each proxy endpoint must do

All 6 Actions Wired — Deploy Proxy to Activate

Every remediation action now POSTs to a specific proxy endpoint with a 10-second timeout, passes the current client's siteId and clientName in the body, and falls back optimistically if the proxy isn't deployed. Each action POST includes X-Dashboard: shadow-ai-gov for proxy routing.

ActionProxy EndpointS1 API TargetConstraint
Isolate affected endpoints via S1 APIPOST /api/shadow-ai/actions/isolatePOST /web/api/v2.1/agents/actions/disconnectRequires agent IDs. Proxy resolves siteId → agentIds via GET /agents?siteIds=.
Kill ollama/lm-studio processes remotelyPOST /api/shadow-ai/actions/kill-processS1 Remote Script LibraryNo direct process-kill-by-PID in S1 API. Proxy must invoke Remote Script execution targeting the agent.
Block port 11434 via S1 Firewall ControlPOST /api/shadow-ai/actions/block-portPOST /web/api/v2.1/firewall-controlRequires Admin or Firewall Control role. Rule must specify portRange, protocol, direction, action:"Block".
Delete .gguf model files (File Remediation)POST /api/shadow-ai/actions/delete-filesS1 Remote Script LibraryNo arbitrary file delete in S1 API. threats/mitigate/remediate works only on threat-tagged files. Proxy must use Remote Script execution.
Send policy acknowledgment request to userPOST /api/shadow-ai/actions/policy-ackPSA / Email workflowNot a S1 API action. Proxy sends email or creates PSA task — no S1 call involved.
Generate incident report for clientPOST /api/shadow-ai/actions/incident-reportPSA / Custom reportNot a S1 API action. Proxy assembles report from collected DV data and creates a PSA ticket or document.
Action request body — sent by every action button
POST /api/shadow-ai/actions/{slug}
Content-Type: application/json
X-Dashboard: shadow-ai-gov

{
  "action":     "isolate",
  "siteId":     "site_acme",    // null if no client is open in deep-dive
  "clientName": "Acme Corp",
  "timestamp":  "2026-03-22T14:00:00.000Z"
}

// Proxy response — optional, shown in toast if present:
{ "message": "3 endpoints isolated · Acme Corp" }
Context Dependency — siteId Required for S1-Targeted Actions

The siteId in the action body is null if no client card is currently open in the Deep Dive panel. For actions that require S1 API calls (isolate, block-port), your proxy must validate that siteId is present before dispatching to S1 — and return a 400 error if it's missing. The dashboard will show that error in the toast. Isolate additionally requires agent IDs, which your proxy must resolve from the site by calling GET /web/api/v2.1/agents?siteIds={siteId} before dispatching the disconnect action.

13

Analytics Charts

Five Chart.js 4.4.1 canvases — data sources and production wiring

Canvas IDTypeDemo Data SourceProduction Source
chartModelsDoughnutHardcoded: [14,5,4,2,3] for Ollama/LM Studio/llama.cpp/Whisper/UnknownDV events — count distinct ProcessName values matching each model
chartTrendLineMath.random() for 30 daysDV events — group by createdAt date, count per day
chartRiskBarclient.detections sorted descendingDV events — count per siteId, sorted by total events
chartCoverageHorizontal Barclient.agentsOnline / client.agentsGET /web/api/v2.1/agents per site — count isActive=true vs total
chartGPUMulti-lineMath.random() per client, 14 daysProcess-correlation DV query for CUDA/torch processes per site per day

All charts use a consistent tooltip theme: dark background (#0a0f18), JetBrains Mono title/body fonts, and subdued label colors to match the console aesthetic. Chart instances are stored in the chartInstances object. Charts are rendered once on init and not re-rendered on scan completion in the current version — add renderCharts() to the scan completion handler to refresh them with real data.

14

Query Builder

Preset selection, DSL textarea, execution, and payload preview

The Query Builder allows analysts to compose, preview, and execute SentinelOne Deep Visibility queries against any Site or the full portfolio. It is wired to POST real queries to your proxy — execute populates the terminal with the real queryId and response, while Preview shows the JSON payload without sending it.

Preset QueriesSeven buttons — Ollama Detection, .gguf Model Files, LM Studio, GPU Abuse, llama.cpp, Whisper/STT, Localhost AI Ports. Clicking a preset loads the validated DSL string into the textarea and calls showPayload() to refresh the terminal preview. All presets use confirmed valid DV DSL operators.
Site Selector"All Sites (Portfolio)" sends ["[all_site_ids]"] as a placeholder — your proxy must expand this to the list of all real site IDs from GET /sites. Selecting a specific site sends that site's ID directly.
Date RangeISO 8601 datetime strings. S1 DV queries are limited to 93 days of historical data per the official documentation. Queries spanning more than 93 days will fail at the API level.
LimitMaximum events to return per query: 50, 100, 250, or 500. S1 DV events endpoint supports pagination via cursor — if pagination.nextCursor is present in the response, more results are available.
▶ Execute QueryPOSTs to /api/shadow-ai/edr/sentinelone/dv/init-query with the current payload. Returns real queryId and initial status. Terminal updates with the actual response.
{ } Preview PayloadCalls showPayload() which builds and displays the JSON payload in the terminal WITHOUT sending it. Use this to inspect and copy the exact request that will be sent.
Copy buttonCopies lastPayload (the most recently previewed or executed query body) to clipboard using navigator.clipboard.writeText().
15

Policy Status Table

Per-client AI governance compliance — how it's currently computed and how to source it live

The Policy Status table shows seven columns per client: Client/Site, Overall Status, AI Policy (written policy on file), S1 Rule (behavioral detection rule deployed), User ACK (employee acknowledgment), Incident (open PSA ticket), and Events. The overall status is derived from client.risk.

In the current version, all columns except Events are derived from client.risk using simple boolean logic — for example, hasPolicy = client.risk === 'low'. For production compliance tracking, these fields should be sourced from your GRC platform, PSA custom fields, or a separate policy management database. The table structure supports direct replacement of the computed values with real data from those systems.

S1 Behavioral Rule Status

The "S1 Rule" column currently shows "Active" for non-critical S1-managed clients. In production, validate this by checking whether a custom STAR (Singularity Threat Analytics Rule) targeting AI process names exists for the site. Use GET /web/api/v2.1/cloud-detection/rules?siteIds={id} to query deployed detection rules. If no rule matching "ollama" or "llama" exists, mark as "Missing".

16

Alert Feed

Detection alerts — demo data structure and production population

The Alert Feed renders the alertsData array — currently seven static entries covering the highest-severity findings across clients. Each entry shows: severity icon, client name, message, endpoint detail (↳ processName · hostname · context), and source metadata (platform, query method, age).

In production, populate alertsData from your DV events response after each scan. Group events by siteId, rank by severity (processes listening on 0.0.0.0 = critical, HIPAA-relevant models on clinical machines = critical, .gguf files = high, policy-violation ports = high, single session clean exit = medium). The endpoint detail and meta strings should be built from the DV event's agentComputerName, processName, and createdAt fields.

17

Deep Visibility DSL

Valid operators, queryable fields, and constraints confirmed against S1 v2.1 API documentation

Valid Operators

OperatorDescriptionExample
ContainsCISCase-insensitive substring matchProcessName ContainsCIS "ollama"
EndWithString ends with value (case-insensitive)FilePath EndWith ".gguf"
INValue is in a set of stringsNetworkPort IN ("11434","1234")
=Exact matchNetworkPort = "11434"
AND / ORBoolean logic — OR has lower precedence than ANDUse parentheses to group OR clauses

Queryable Fields (Process Events)

FieldDescription
ProcessNameExecutable name of the process (e.g. "ollama.exe")
ProcessCmdLineFull command line including arguments — good for catching model loading flags
FilePathFull path of file accessed or created by the process
NetworkPortPort number the process is listening on or connecting to
InitiatingProcessNameParent process name — useful for detecting scripts that launch AI runtimes
InitiatingProcessFileNameParent process executable filename
SrcIP / DstIPSource/destination IP — useful for detecting external connections to local model APIs
SrcPort / DstPortSource/destination port for network events
SHA1 / SHA256File hashes — for blocklist matching of known model executables
GPUUsage Is Not a Valid DV Field

GPUUsage does not exist in the SentinelOne Deep Visibility query schema. GPU utilization metrics are not exposed through the DV API. A query containing GPUUsage > 60 returns zero results without an error, silently appearing to show a clean environment. For GPU anomaly detection, use process-based correlation: identify processes known to use GPU acceleration (Python with torch/CUDA args, ollama, llama-server) rather than querying GPU metrics directly.

Query Limitations

93-day windowDeep Visibility queries are limited to the last 93 days of telemetry. Queries spanning older data will fail or return empty results.
AsynchronousDV is not synchronous — init-query returns a queryId immediately, results are not available until the query status is FINISHED. Always poll dv/query-status before fetching events.
Deep Visibility licenseDV must be licensed and enabled per-site in the S1 management console. Sites without DV enabled will return no events even if the query succeeds. Verify: Sentinels → Policy → Deep Visibility → Enabled.
PaginationResults are paginated. If pagination.nextCursor is present in the events response, additional results exist. Pass cursor={value} as a query param on subsequent requests to retrieve the next page.
18

Preset Query Library

Seven validated queries — what each detects and why

Preset KeyQuery (DSL)Detects
ollamaProcessName ContainsCIS "ollama" OR ProcessCmdLine ContainsCIS "ollama" OR NetworkPort = "11434"Ollama runtime — process running, started via script, or port 11434 active. Port match catches the API server even if the process name differs.
ggufFilePath EndWith ".gguf" OR FilePath EndWith ".bin" AND FilePath ContainsCIS "models"GGUF model files (Ollama, llama.cpp, LM Studio format) and .bin model files in model directories.
lmstudioProcessName ContainsCIS "lm studio" OR ProcessName ContainsCIS "LMStudio" OR NetworkPort = "1234"LM Studio — process name (space variant and concatenated) or default API port 1234.
gpuProcessName ContainsCIS "python" AND (ProcessCmdLine ContainsCIS "torch" OR ProcessCmdLine ContainsCIS "cuda" OR ProcessCmdLine ContainsCIS "gpu") OR ProcessName ContainsCIS "ollama" OR ProcessName ContainsCIS "llama-server"GPU-intensive AI processes — Python with ML framework args, or known AI runtimes. Replaces the invalid GPUUsage > 60 query.
llamacppProcessName ContainsCIS "llama-server" OR ProcessName ContainsCIS "llama.cpp" OR ProcessCmdLine ContainsCIS "gguf"llama.cpp server process, the llama.cpp binary, or any process whose command line references a .gguf file path.
whisperProcessName ContainsCIS "whisper" OR ProcessCmdLine ContainsCIS "whisper" OR FilePath ContainsCIS "openai/whisper"OpenAI Whisper STT — process, command line, or model file path. Critical for HIPAA environments where PHI may be in audio.
localportNetworkPort IN ("11434","1234","8080","5000","3000") AND InitiatingProcessName ContainsCIS "python"Common local AI API ports bound by Python processes. Catches custom AI server deployments that don't use known binary names.
19

Defender Fallback

What it means, what it does now, and how to wire it

One client in the demo data — Mu Manufacturing — uses edr: 'defender'. This represents clients whose endpoints run Microsoft Defender for Endpoint rather than SentinelOne. The console displays a yellow "● Defender · Fallback EDR" badge for these clients and shows their static detection data — but does not actively query Microsoft Graph Advanced Hunting.

Defender Integration Is Not Yet Implemented

The API status banner shows Defender: FALLBACK with a yellow dot and labels the endpoint as "Microsoft Graph Advanced Hunting" — but no actual Graph API calls are made anywhere in the current codebase. The Defender client's data is entirely static. To implement this, your proxy must call POST https://graph.microsoft.com/v1.0/security/runHuntingQuery with a KQL query equivalent to the S1 DV DSL query, using an app registration with the ThreatHunting.Read.All permission. The console filter already supports edr: 'defender' — wire the data population in your proxy and push the results into the client array via a scan completion callback.

Microsoft Graph Advanced Hunting — KQL Equivalents

S1 DV DSL QueryMicrosoft Graph / KQL Equivalent
ProcessName ContainsCIS "ollama"DeviceProcessEvents | where FileName contains "ollama"
FilePath EndWith ".gguf"DeviceFileEvents | where FileName endswith ".gguf"
NetworkPort = "11434"DeviceNetworkEvents | where LocalPort == 11434 or RemotePort == 11434
ProcessCmdLine ContainsCIS "whisper"DeviceProcessEvents | where ProcessCommandLine contains "whisper"
20

Proxy Endpoint Spec

Every path the dashboard calls — what your proxy must implement

Read Endpoints — Viewer role sufficient
GET /api/shadow-ai/edr/sentinelone/sitesproxyForward to GET /web/api/v2.1/sites?limit=1000&state=active. Return the full sites array. Used by runScan to count clients and build siteId list.
GET /api/shadow-ai/edr/sentinelone/agentsproxyForward to GET /web/api/v2.1/agents?siteIds=all&limit=1000 (or per-site). Return agent count by site. Used by runScan for inventory.
POST /api/shadow-ai/edr/sentinelone/dv/init-queryproxyForward POST body to POST /web/api/v2.1/dv/init-query. Return {data:{queryId, status}}. Body fields: query, fromDate, toDate, siteIds, limit.
GET /api/shadow-ai/edr/sentinelone/dv/query-statusproxyForward to GET /web/api/v2.1/dv/query-status?queryId={queryId}. Return {data:{status}} where status is RUNNING or FINISHED.
GET /api/shadow-ai/edr/sentinelone/dv/eventsproxyForward to GET /web/api/v2.1/dv/events?queryId={id}&limit={n}. Return events array and pagination object.
Action Endpoints — Elevated role required
POST /api/shadow-ai/actions/isolateaction1) GET /web/api/v2.1/agents?siteIds={body.siteId} to get agentIds. 2) POST /web/api/v2.1/agents/actions/disconnect with {filter:{ids:[agentIds]}}. Returns count of isolated agents.
POST /api/shadow-ai/actions/block-portactionPOST /web/api/v2.1/firewall-control with {data:{name:"Block AI Ports",rules:[{portRange:"11434",protocol:"TCP",direction:"inbound",action:"Block"}],siteIds:[body.siteId]}}.
POST /api/shadow-ai/actions/kill-processactionUse S1 Remote Script Library. POST /web/api/v2.1/remote-scripts/execute targeting the affected agent with a script that kills the process by name.
POST /api/shadow-ai/actions/delete-filesactionUse S1 Remote Script Library. POST /web/api/v2.1/remote-scripts/execute with a script that removes .gguf files from the Ollama models directory.
POST /api/shadow-ai/actions/policy-ackactionNo S1 call. Send policy acknowledgment email to affected user or create PSA task. Return message confirming dispatch.
POST /api/shadow-ai/actions/incident-reportactionNo S1 call. Assemble DV event data for the client into a report structure and create a PSA ticket. Return ticket ID or report URL in message field.
Common Headers — All Requests

Every call from the dashboard includes Content-Type: application/json and X-Dashboard: shadow-ai-gov. Your proxy should validate the X-Dashboard header as a routing signal and basic anti-CSRF measure. All action endpoints also receive an 10-second AbortSignal.timeout, so your proxy must respond within that window.

21

Go-Live Checklist

Ordered steps from demo mode to production

  1. 01
    Create S1 Service User and generate API token
    Account-scoped Viewer role for read operations. Create a second Service User with a custom role that includes Endpoints → Disconnect from Network and Firewall Control for remediation actions. Store both tokens in your vault.
  2. 02
    Enable Deep Visibility on all client Sites
    In S1 Management Console: Sentinels → Policy → Deep Visibility → Enabled. DV must be both licensed and enabled per-Site. Without this, dv/init-query will succeed but dv/events will return empty results.
  3. 03
    Deploy the backend proxy
    Implement all 10 endpoints from Section 20. Serve from the same origin as the dashboard HTML or configure a reverse proxy so relative paths resolve correctly. The token must live only in the proxy's environment — never in the browser.
  4. 04
    Map client names to real S1 Site IDs
    Run GET /web/api/v2.1/sites via your proxy and map the returned site IDs to the client records in the clients array. Update the siteId values from the placeholder strings (site_acme, site_zeta, etc.) to your real numeric S1 Site IDs. Update the Query Builder's site dropdown options to match.
  5. 05
    Wire renderClientGrid to DV event data
    After a scan completes, transform DV events into the client array shape (detections, models[], endpoints[], events[], gpuFlag, port11434, ggufFiles). Call renderClientGrid(clients) and renderKPIs() with the updated data. The cards and KPI tiles will reflect live scan results automatically.
  6. 06
    Deploy action proxy endpoints
    Implement the six /api/shadow-ai/actions/* routes. Test each one manually before enabling in the console. The dashboard falls back gracefully if the routes aren't yet live — no frontend change needed when you add them.
  7. 07
    Update API status banner to reflect real health
    Add a lightweight proxy health endpoint and call it from renderApiBanner() on page load. Update node dot classes (dot-live / dot-mock / dot-error) based on the health response rather than static strings.
  8. 08
    Wire Analytics Charts to DV events data
    After a scan completes, aggregate DV event data into the chart shapes documented in Section 13 and call renderCharts() with the live data. The chart canvases and Chart.js instances are already set up — you only need to provide real data arrays.
22

FAQ

Common questions about wiring, limitations, and edge cases

The scan overlay appears then disappears immediately — no data changes

Your proxy is not deployed — the fetch('/api/shadow-ai/edr/sentinelone/sites') call on the first scan step fails immediately with a network error, triggering the fallback which closes the overlay and re-renders demo data. Deploy the proxy at /api/shadow-ai/edr/sentinelone/sites and the scan will proceed through all five steps.

dv/init-query returns 200 but dv/events returns empty results

Deep Visibility is not enabled for that Site. Verify in the S1 Management Console under Sentinels → Policy → Deep Visibility. The policy must explicitly have Deep Visibility enabled — it is not on by default. Also confirm the Service User has the Deep Visibility license scope at the Account level.

The GPU preset query returns zero results even though AI tools are present

The original GPUUsage > 60 field is not valid in S1 DV DSL — it would always return zero. The corrected query uses process-name and command-line matching for known GPU-intensive processes. If you're still seeing zero results, verify that Deep Visibility is collecting process command line data for the relevant Site (check the DV event type configuration in the Site policy).

Isolate action succeeds at the proxy but the endpoint wasn't isolated

The disconnect action requires the Service User token to have the "Disconnect from Network" permission in its role. The built-in Viewer role does not include this. Create a custom role with Endpoints → Disconnect from Network checked, assign it to the Service User used for remediation, and use that token for action routes. Keep a separate read-only Viewer token for DV query routes.

Can I add a new AI tool to detect (e.g. Jan.ai, GPT4All)?

Yes. Add a new entry to the presets object with a DSL query targeting the tool's process name or typical file paths. Add a corresponding button in the Preset Queries row HTML. No other changes needed — the query builder, terminal, and scan flow all work with arbitrary valid DV DSL strings. Verify any new query operators against the field list in Section 17 before adding.

S1 DV query rate limits and timing considerations

SentinelOne DV queries are resource-intensive — running a full portfolio-wide scan against 8+ sites on every page load would be inappropriate. The console is designed for on-demand scanning (the "Run Deep Visibility Scan" button) rather than continuous polling. For automated periodic scans, run the query cycle from your proxy on a cron schedule (e.g. every 30 minutes), cache the results, and expose them at a lightweight GET /api/shadow-ai/scan-results endpoint that the dashboard polls instead of initiating a new DV query each time.