Skip to Content
OpenVAPTAlpha
Pre-release. Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. Free for your own use, 100% at your own risk. This website collects no data. Read MoreRead Less
Pre-release. OpenVAPT has no Stable release yet. Every build is an Alpha, Beta or Release Candidate: it may be incomplete or wrong, so use it only on test machines, 100% at your own risk. See Release Channels
Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. These are AI systems, not people or registered engineers. Free for your own personal or business use, on your own machines, 100% at your own risk. Not for sale. Not for providing services. This website collects no data. Read the Acknowledgements
Pre-release. OpenVAPT has no Stable release yet. Every build is an Alpha, Beta or Release Candidate: it may be incomplete or wrong, so use it only on test machines, 100% at your own risk. See Release Channels
Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. These are AI systems, not people or registered engineers. Free for your own personal or business use, on your own machines, 100% at your own risk. Not for sale. Not for providing services. This website collects no data. Read the Acknowledgements
HighNET-AI-001Network ExposureNetwork

AI Model Server or Dashboard Reachable From the Network

Checks whether a self-hosted AI model server, AI web interface, AI cluster dashboard or vector database listens on a network-reachable address.

Why It Matters
Many self-hosted AI tools have no login enabled by default. Reached from the network, anyone can use the models and the computing power, read stored documents and embeddings, pull or delete models, and on some dashboards submit jobs that run code.
How It Detects
Matches network-reachable listening TCP sockets from /proc/net against the documented default ports of Ollama, LM Studio, Gradio, ComfyUI, the Ray dashboard, Qdrant, Milvus and TensorBoard, and records how each is bound. No request, prompt or model call is sent.
How to Fix
Bind each service to 127.0.0.1 (Ollama: OLLAMA_HOST=127.0.0.1:11434 in its service environment; Ray: --dashboard-host=127.0.0.1; Gradio: server_name="127.0.0.1"), and if others need it, publish it only through a reverse proxy with authentication and TLS.
How to Verify
ss -tlnp shows these ports on 127.0.0.1 or ::1 only.
CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L
Control Category
Network Exposure: CIS Controls v8 4, 12, 13; NIST CSF 2.0 PR.IR, PR.PS
Fix Effort
Low
CIS Controls v8 Safeguard
4.8
NIST CSF 2.0 Subcategory
PR.PS-01, PR.IR-01
CWE
CWE-306, CWE-1327
OWASP Top 10
A05:2021, A07:2021
NIST SP 800-53 Rev. 5
SC-7, CM-7, AC-3
Benchmark Topic
Expose applications only through a hardened front end (topic only, no benchmark text)
References
Ollama FAQ, How do I configure Ollama server?
Ray documentation, Security
OWASP Top 10 for Large Language Model Applications
Learn the theory behind this check: P24 Securing Self-Hosted AI Services.

Source Code

The complete file that defines this check. Run it with openvapt scan --only NET-AI-001, or read it offline with openvapt explain NET-AI-001.

internal/checks/network/ai_services.go · Lines 1-129View on GitHub at This Commit
1package network
2
3import (
4 "context"
5 "regexp"
6 "strings"
7
8 "github.com/openvapt/openvapt/internal/check"
9 "github.com/openvapt/openvapt/internal/sys"
10)
11
12// NET-AI: self-hosted AI services. Detection only: listening sockets are read
13// from /proc/net and configuration files are read from disk. No request,
14// prompt or model call is ever sent to any AI service.
15
16// aiPorts are the documented default ports of self-hosted AI model servers,
17// AI web interfaces, AI cluster dashboards and vector databases. Most of them
18// ship with no authentication and are designed to listen on loopback only.
19var aiPorts = map[int]string{
20 11434: "Ollama model API",
21 1234: "LM Studio local server",
22 7860: "Gradio AI web interface",
23 8188: "ComfyUI image generation interface",
24 8265: "Ray dashboard and jobs API",
25 6333: "Qdrant vector database",
26 19530: "Milvus vector database",
27 6006: "TensorBoard",
28}
29
30var jupyterPaths = []string{
31 "/etc/jupyter/jupyter_server_config.py", "/etc/jupyter/jupyter_notebook_config.py",
32 "/usr/local/etc/jupyter/jupyter_server_config.py", "/usr/local/etc/jupyter/jupyter_notebook_config.py",
33 "/root/.jupyter/jupyter_server_config.py", "/root/.jupyter/jupyter_notebook_config.py",
34 "/home/*/.jupyter/jupyter_server_config.py", "/home/*/.jupyter/jupyter_notebook_config.py",
35}
36
37// jupyterSetting matches one assignment such as c.ServerApp.token = ”.
38var jupyterSetting = regexp.MustCompile(`^\s*c\.(ServerApp|NotebookApp|IdentityProvider|PasswordIdentityProvider)\.(token|password|hashed_password|disable_check_xsrf)\s*=\s*(.+?)\s*$`)
39
40// jupyterAuthOff reads Jupyter configuration text and reports, without
41// copying any value, whether the token is blanked with no password set, or
42// whether cross-site request forgery protection is turned off.
43func jupyterAuthOff(text string) []string {
44 emptyToken, password, xsrfOff := false, false, false
45 for _, line := range strings.Split(text, "\n") {
46 m := jupyterSetting.FindStringSubmatch(line)
47 if m == nil {
48 continue
49 }
50 v := strings.TrimSpace(strings.SplitN(m[3], "#", 2)[0])
51 blank := v == "''" || v == `""` || v == "u''"
52 switch m[2] {
53 case "token":
54 emptyToken = blank
55 case "password", "hashed_password":
56 password = !blank
57 case "disable_check_xsrf":
58 xsrfOff = v == "True"
59 }
60 }
61 var out []string
62 if emptyToken && !password {
63 out = append(out, "token is an empty string and no login password is set, so anyone who reaches the server can run code")
64 }
65 if xsrfOff {
66 out = append(out, "disable_check_xsrf = True: other websites can send requests to the server through a visitor's browser")
67 }
68 return out
69}
70
71func init() {
72 check.Register(&check.Check{
73 ID: "NET-AI-001", Title: "AI Model Server or Dashboard Reachable From the Network", Domain: check.DomainNetwork,
74 Severity: check.High, Category: check.CatNetwork, Effort: check.EffortLow,
75 CVSS: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L",
76 Summary: "Checks whether a self-hosted AI model server, AI web interface, AI cluster dashboard or vector database listens on a network-reachable address.",
77 Rationale: "Many self-hosted AI tools have no login enabled by default. Reached from the network, anyone can use the models and the computing power, read stored documents and embeddings, pull or delete models, and on some dashboards submit jobs that run code.",
78 Detection: "Matches network-reachable listening TCP sockets from /proc/net against the documented default ports of Ollama, LM Studio, Gradio, ComfyUI, the Ray dashboard, Qdrant, Milvus and TensorBoard, and records how each is bound. No request, prompt or model call is sent.",
79 Fix: "Bind each service to 127.0.0.1 (Ollama: OLLAMA_HOST=127.0.0.1:11434 in its service environment; Ray: --dashboard-host=127.0.0.1; Gradio: server_name=\"127.0.0.1\"), and if others need it, publish it only through a reverse proxy with authentication and TLS.",
80 Verify: "ss -tlnp shows these ports on 127.0.0.1 or ::1 only.",
81 Refs: []check.Ref{{Title: "Ollama FAQ, How do I configure Ollama server?", URL: "https://github.com/ollama/ollama/blob/main/docs/faq.md"},
82 {Title: "Ray documentation, Security", URL: "https://docs.ray.io/en/latest/ray-security/index.html"},
83 {Title: "OWASP Top 10 for Large Language Model Applications", URL: "https://genai.owasp.org/llm-top-10/"}},
84 Standards: check.Standards{CWE: []string{"CWE-306", "CWE-1327"}, OWASP: []string{"A05:2021", "A07:2021"}, NIST: []string{"SC-7", "CM-7", "AC-3"}, CISTopic: "Expose applications only through a hardened front end"},
85 Run: appPortRun(aiPorts, "AI services are network-reachable", "No AI model server, dashboard or vector database port is network-reachable"),
86 })
87
88 check.Register(&check.Check{
89 ID: "NET-AI-002", Title: "Jupyter Configuration Weakens Authentication", Domain: check.DomainNetwork,
90 Severity: check.High, Category: check.CatIdentity, Effort: check.EffortLow,
91 CVSS: "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
92 Summary: "Checks whether a Jupyter configuration blanks the login token without setting a password, or turns off cross-site request protection.",
93 Rationale: "Jupyter runs any code typed into it with the rights of the account that started it. With the token blanked and no password, anyone who can reach the port, or any local user, gets that account. With XSRF checks off, a web page a user visits can send it commands.",
94 Detection: "Reads Jupyter configuration files under /etc/jupyter, /usr/local/etc/jupyter, /root/.jupyter and /home/*/.jupyter and looks for token, password and disable_check_xsrf assignments. Only whether a value is empty is recorded; no token or password is copied.",
95 Fix: "Remove the empty token line so Jupyter generates a random token, or set a password with jupyter server password . Remove disable_check_xsrf = True. Bind to 127.0.0.1 and reach it through an SSH tunnel.",
96 Verify: "grep -nE 'token|password|disable_check_xsrf' <config file> shows no empty token and no disabled XSRF check, and the server log prints a URL with a token at start-up.",
97 Refs: []check.Ref{{Title: "Jupyter Server documentation, Security in the Jupyter Server", URL: "https://jupyter-server.readthedocs.io/en/latest/operators/security.html"}},
98 Standards: check.Standards{CWE: []string{"CWE-306", "CWE-352"}, OWASP: []string{"A07:2021", "A05:2021"}, NIST: []string{"IA-2", "AC-3"}, CISTopic: "Require authentication for administrative and code execution interfaces"},
99 Run: func(ctx context.Context, s sys.System) []check.Result {
100 var paths []string
101 for _, p := range jupyterPaths {
102 if strings.Contains(p, "*") {
103 m, _ := s.Glob(p)
104 paths = append(paths, m...)
105 } else {
106 paths = append(paths, p)
107 }
108 }
109 var res []check.Result
110 for _, p := range paths {
111 r, err := s.ReadFile(ctx, p)
112 if err != nil {
113 continue
114 }
115 if ev := jupyterAuthOff(string(r.Data)); len(ev) > 0 {
116 res = append(res, check.FailR(p, "Jupyter configuration weakens authentication or request forgery protection", ev...))
117 } else if !r.Complete {
118 res = append(res, check.IncompleteR(p, "Configuration input was truncated"))
119 } else {
120 res = append(res, check.PassR(p, "Jupyter keeps token or password authentication and XSRF protection"))
121 }
122 }
123 if len(res) == 0 {
124 return []check.Result{check.SkipR("jupyter configuration", "No Jupyter configuration file was found")}
125 }
126 return res
127 },
128 })
129}

Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.

Cite This Page

If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.

APA 7
OpenVAPT. (2026). NET-AI-001 AI Model Server or Dashboard Reachable From the Network (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/NET-AI-001.html

In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.

BibTeX
@misc{openvapt_net_ai_001,
  author       = {{OpenVAPT}},
  title        = {{NET-AI-001 AI Model Server or Dashboard Reachable From the Network}},
  year         = {2026},
  howpublished = {\url{https://openvapt.com/checks/NET-AI-001.html}},
  note         = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}

Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.

Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.