feat(privacy): surface the customer-data scan above the hardware report
- Move the "Customer data" panel to the top of the data section (above the chart iframe); header + customer guess always visible, findings table collapsed by default and expandable. - Add a chart top-notice (above Board/CPUs) summarizing the scan via the viewer's standard NoticeTitle/NoticeBody - interim until chart custom panels. - Allowlist ieisystem.com (IEI = Inspur brand infrastructure). - Add chart-custom-panels-spec.md: a reusable, versioned contract proposal for host-supplied panels across every app embedding reanimator/chart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
4a4910f207
commit
fb0b0e3c55
@@ -19,7 +19,7 @@ var (
|
||||
"libssh.org", "rsyslog.com", "adiscon.com", "redhat.com", "kernel.org",
|
||||
"megarac.com", "ami.com", "commond.com",
|
||||
"oasis-open.org", "w3.org", "xmlsoap.org", "purl.org", "ietf.org",
|
||||
"inspur.com", "inspurcloud.com", "inservice-iq.com", "kaytus.com",
|
||||
"inspur.com", "inspurcloud.com", "inservice-iq.com", "ieisystem.com", "kaytus.com",
|
||||
"jd.com", "jd.local", "jdcloud.com", "in-addr.arpa", "ip6.arpa", "arpa",
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,13 @@ func (s *Server) handleChartCurrent(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
html, err := chartviewer.RenderHTMLWithOptions(snapshotBytes, title, chartviewer.RenderOptions{})
|
||||
opts := chartviewer.RenderOptions{}
|
||||
if nt, nb := privacyNotice(result.PrivacyScan); nt != "" {
|
||||
opts.NoticeTitle = nt
|
||||
opts.NoticeBody = nb
|
||||
}
|
||||
|
||||
html, err := chartviewer.RenderHTMLWithOptions(snapshotBytes, title, opts)
|
||||
if err != nil {
|
||||
s.htmlError(w, "failed to render chart: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -95,6 +101,68 @@ func (s *Server) handleChartCurrent(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(rewriteChartStaticPaths(html))
|
||||
}
|
||||
|
||||
// privacyNotice renders the customer-data scan summary as the chart viewer's
|
||||
// top notice panel (above the Board/CPUs sections). Uses the viewer's standard
|
||||
// NoticeTitle/NoticeBody mechanism - no chart changes. Full findings stay in the
|
||||
// "Customer data" panel and GET /api/privacy-scan.
|
||||
func privacyNotice(scan *models.PrivacyScan) (title, body string) {
|
||||
if scan == nil || (scan.Summary.Total == 0 && len(scan.Customers) == 0) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
title = "Customer data detected"
|
||||
if len(scan.Customers) > 0 {
|
||||
title += " - likely " + scan.Customers[0].Domain
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "%d finding(s)", scan.Summary.Total)
|
||||
sev := make([]string, 0, 3)
|
||||
if scan.Summary.High > 0 {
|
||||
sev = append(sev, fmt.Sprintf("%d high", scan.Summary.High))
|
||||
}
|
||||
if scan.Summary.Medium > 0 {
|
||||
sev = append(sev, fmt.Sprintf("%d medium", scan.Summary.Medium))
|
||||
}
|
||||
if scan.Summary.Low > 0 {
|
||||
sev = append(sev, fmt.Sprintf("%d low", scan.Summary.Low))
|
||||
}
|
||||
if len(sev) > 0 {
|
||||
fmt.Fprintf(&b, " (%s)", strings.Join(sev, ", "))
|
||||
}
|
||||
fmt.Fprintf(&b, " across %d file(s).", scan.FilesScanned)
|
||||
if cats := topCategories(scan.Summary.ByCategory, 4); len(cats) > 0 {
|
||||
fmt.Fprintf(&b, " Categories: %s.", strings.Join(cats, ", "))
|
||||
}
|
||||
b.WriteString(` Review the "Customer data" panel and sanitize this dump before sharing it.`)
|
||||
return title, b.String()
|
||||
}
|
||||
|
||||
func topCategories(byCategory map[string]int, n int) []string {
|
||||
type kv struct {
|
||||
k string
|
||||
v int
|
||||
}
|
||||
items := make([]kv, 0, len(byCategory))
|
||||
for k, v := range byCategory {
|
||||
items = append(items, kv{k, v})
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
if items[i].v != items[j].v {
|
||||
return items[i].v > items[j].v
|
||||
}
|
||||
return items[i].k < items[j].k
|
||||
})
|
||||
out := make([]string, 0, n)
|
||||
for i, it := range items {
|
||||
if i >= n {
|
||||
break
|
||||
}
|
||||
out = append(out, it.k)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func currentReanimatorSnapshotBytes(result *models.AnalysisResult) ([]byte, error) {
|
||||
reanimatorData, err := exporter.ConvertToReanimator(result)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user