ONNX
security
malware-detection
File size: 5,720 Bytes
d2507b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package bundle

import (
	"encoding/json"
	"regexp"
	"strings"
)

// DataAnalyzer inspects data/text siblings (json/yaml/csv/txt/.ipynb). A
// referenced data file IS the context-loader vector: an agent told to "read the
// steps in reference.yaml" will follow imperative directives embedded in it. So
// the CONTENT is scanned for imperative run/exec/fetch directives, exfil hosts,
// and base64/long-hex blobs. .ipynb is parsed as JSON and its code-cell source
// is scanned with the script vocabulary. A standalone present-but-unreferenced
// hit is SevMedium; IndirectionAnalyzer raises delegate-to-data to SevHigh when
// SKILL.md delegates to the file.
type DataAnalyzer struct{}

func (DataAnalyzer) Name() string { return "data" }

func (DataAnalyzer) Handles(kind FileKind) bool {
	return kind == KindData || kind == KindText
}

// imperativeDirectiveRe matches instructions telling the agent to run/execute/
// source/fetch something — the heart of the context-loader bypass.
var imperativeDirectiveRe = regexp.MustCompile(`(?i)\b(run|execute|exec|source|eval|invoke|fetch|download|curl|wget|pipe to (?:sh|bash)|chmod \+x|install (?:and run)?|launch|spawn)\b`)

func (DataAnalyzer) Analyze(f *File, b *Bundle) ([]Finding, error) {
	if f == nil {
		return nil, nil
	}
	text := string(f.Sniff)
	var out []Finding

	// Exfil host anywhere in a data file is critical.
	if exfilHostRe.MatchString(text) {
		line := firstLineMatching(text, exfilHostRe)
		out = append(out, Finding{
			Analyzer:     "data",
			File:         f.RelPath,
			Signal:       "exfil-host-reference",
			Severity:     SevCritical,
			Detail:       "data file references known exfiltration host",
			Line:         line,
			Corroborated: true,
		})
	}

	// .ipynb: parse code cells and scan their source with the script vocab.
	if strings.HasSuffix(strings.ToLower(f.RelPath), ".ipynb") {
		cells := scanNotebookCells(f.Sniff)
		if len(cells) == 0 && looksLikeJSON(text) {
			out = append(out, Finding{
				Analyzer: "data",
				File:     f.RelPath,
				Signal:   "opaque-notebook",
				Severity: SevMedium,
				Detail:   "could not parse notebook cells (malformed JSON)",
				Opaque:   true,
			})
		}
		for _, src := range cells {
			cellFindings := sharedIndicatorScan(src, f.RelPath, "data")
			out = append(out, cellFindings...)
		}
	}

	// Imperative directives embedded in data values.
	for _, dir := range scanImperativeDirectives(text) {
		out = append(out, Finding{
			Analyzer: "data",
			File:     f.RelPath,
			Signal:   "data-embedded-directive",
			Severity: SevMedium,
			Detail:   "data file embeds an imperative agent directive: " + dir,
		})
	}

	// base64/hex payload carriers inside data values.
	lines := strings.Split(text, "\n")
	for i, l := range lines {
		if base64BlobRe.MatchString(l) || longHexRe.MatchString(l) {
			out = append(out, Finding{
				Analyzer: "data",
				File:     f.RelPath,
				Signal:   "embedded-encoded-blob",
				Severity: SevMedium,
				Detail:   "long base64/hex blob inside data file",
				Line:     i + 1,
			})
		}
	}

	// Decode-and-rescan the data body (covers json/yaml/csv/txt carriers and the
	// split-runs obfuscation). A behavioral hit in the recovered bytes adds a
	// corroborated SevHigh decoded-* finding so an obfuscated directive escalates.
	out = append(out, decodeAndRescan(text, f.RelPath, "data", 0)...)

	if pe, ok := paddingEvasionFinding(f, "data"); ok {
		out = append(out, pe)
	}
	return dedupeFindings(out), nil
}

// scanImperativeDirectives returns deduped snippets of imperative directives
// found in the text (capped to keep output bounded).
func scanImperativeDirectives(text string) []string {
	seen := map[string]bool{}
	var out []string
	for _, l := range strings.Split(text, "\n") {
		if !imperativeDirectiveRe.MatchString(l) {
			continue
		}
		// Only flag lines that also carry a URL, a path, or a shell-ish token —
		// a prose word "run the report" alone is not a directive.
		low := strings.ToLower(l)
		if !urlRe.MatchString(l) &&
			!strings.Contains(low, "./") &&
			!strings.Contains(low, ".sh") &&
			!strings.Contains(low, ".py") &&
			!matchedAny(low, networkSinkTerms) &&
			!matchedAny(low, rceTerms) {
			continue
		}
		snippet := strings.TrimSpace(l)
		if len(snippet) > 120 {
			snippet = snippet[:120]
		}
		if !seen[snippet] {
			seen[snippet] = true
			out = append(out, snippet)
		}
		if len(out) >= 16 {
			break
		}
	}
	return out
}

// scanNotebookCells parses a Jupyter notebook and returns the concatenated
// source of each code cell. Degrades to empty on malformed JSON (never panics).
func scanNotebookCells(jsonBytes []byte) []string {
	var nb struct {
		Cells []struct {
			CellType string          `json:"cell_type"`
			Source   json.RawMessage `json:"source"`
		} `json:"cells"`
	}
	if err := json.Unmarshal(jsonBytes, &nb); err != nil {
		return nil
	}
	var out []string
	for _, c := range nb.Cells {
		if c.CellType != "code" {
			continue
		}
		out = append(out, joinSource(c.Source))
	}
	return out
}

// joinSource handles the notebook "source" field which may be a string or an
// array of strings.
func joinSource(raw json.RawMessage) string {
	if len(raw) == 0 {
		return ""
	}
	var asString string
	if err := json.Unmarshal(raw, &asString); err == nil {
		return asString
	}
	var asArray []string
	if err := json.Unmarshal(raw, &asArray); err == nil {
		return strings.Join(asArray, "")
	}
	return ""
}

func looksLikeJSON(text string) bool {
	t := strings.TrimSpace(text)
	return strings.HasPrefix(t, "{") || strings.HasPrefix(t, "[")
}

func firstLineMatching(text string, re *regexp.Regexp) int {
	for i, l := range strings.Split(text, "\n") {
		if re.MatchString(l) {
			return i + 1
		}
	}
	return 0
}