packetsnitch 1.5.599

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.eslintrc.json +28 -0
  2. package/.webpack/x64/main/index.js +2 -0
  3. package/.webpack/x64/main/index.js.map +1 -0
  4. package/.webpack/x64/renderer/assets/css/rubikglitch.woff2 +0 -0
  5. package/.webpack/x64/renderer/assets/css/style.css +1916 -0
  6. package/.webpack/x64/renderer/assets/images/loading.gif +0 -0
  7. package/.webpack/x64/renderer/assets/images/logo.webp +0 -0
  8. package/.webpack/x64/renderer/assets/images/packet-snitch-tag.webp +0 -0
  9. package/.webpack/x64/renderer/main_window/index.html +3 -0
  10. package/.webpack/x64/renderer/main_window/index.js +3 -0
  11. package/.webpack/x64/renderer/main_window/index.js.LICENSE.txt +36 -0
  12. package/.webpack/x64/renderer/main_window/index.js.map +1 -0
  13. package/.webpack/x64/renderer/main_window/preload.js +2 -0
  14. package/.webpack/x64/renderer/main_window/preload.js.map +1 -0
  15. package/backend/common/GeoLite2-City.mmdb +0 -0
  16. package/backend/common/mac-vendors-export.csv +56923 -0
  17. package/backend/common/service-names-port-numbers.csv +15368 -0
  18. package/backend/requirements.txt +14 -0
  19. package/backend/snitch.py +3611 -0
  20. package/forge.config.js +80 -0
  21. package/package.json +102 -0
  22. package/ps-icon.ico +0 -0
  23. package/snitch.spec +44 -0
  24. package/src/assets/css/rubikglitch.woff2 +0 -0
  25. package/src/assets/css/style.css +1916 -0
  26. package/src/assets/images/loading.gif +0 -0
  27. package/src/assets/images/logo.webp +0 -0
  28. package/src/assets/images/packet-snitch-tag.webp +0 -0
  29. package/src/back-comm.js +70 -0
  30. package/src/decoders.js +579 -0
  31. package/src/filter.js +461 -0
  32. package/src/front.js +10 -0
  33. package/src/index.html +1036 -0
  34. package/src/logging.js +150 -0
  35. package/src/main.js +571 -0
  36. package/src/preload.js +73 -0
  37. package/src/renderer.js +30 -0
  38. package/src/ui/common-frontend.js +13 -0
  39. package/src/ui/context-menu.js +88 -0
  40. package/src/ui/decoders.js +1 -0
  41. package/src/ui/main-frontend.js +4957 -0
  42. package/src/ui/panels/crypt-panel.js +565 -0
  43. package/src/ui/panels/data-panel.js +151 -0
  44. package/src/ui/panels/data-tools-panel.js +939 -0
  45. package/src/ui/panels/install-screen.js +59 -0
  46. package/src/ui/panels/keystore-panel.js +1248 -0
  47. package/src/ui/panels/list-panel.js +403 -0
  48. package/src/ui/panels/stats-panel.js +351 -0
  49. package/src/ui/panels/summary-panel.js +63 -0
  50. package/webpack.main.config.js +11 -0
  51. package/webpack.plugins.js +13 -0
  52. package/webpack.preload.config.js +7 -0
  53. package/webpack.renderer.config.js +30 -0
  54. package/webpack.rules.js +35 -0
@@ -0,0 +1,351 @@
1
+ function normalizeStatsTextValue(value, options = {}) {
2
+ if (value === null || value === undefined) return null;
3
+
4
+ const { stripNonPrintable = false } = options;
5
+ let normalized = typeof value === "string" ? value : String(value);
6
+
7
+ if (stripNonPrintable) {
8
+ normalized = normalized.replace(/[\x00-\x1F\x7F]/g, "");
9
+ }
10
+
11
+ normalized = normalized.trim();
12
+ return normalized ? normalized : null;
13
+ }
14
+
15
+ function normalizeStatsPortValue(value) {
16
+ if (value === null || value === undefined) return null;
17
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
18
+ const normalizedText = normalizeStatsTextValue(value);
19
+ if (!normalizedText || !/^\d+$/.test(normalizedText)) return null;
20
+ return Number(normalizedText);
21
+ }
22
+
23
+ function buildCaptureStats(capturedPackets) {
24
+ const protocols = new Set();
25
+ const transportProtocols = new Set();
26
+ const hosts = new Set();
27
+ const ports = new Set();
28
+ const macVendors = new Set();
29
+ const mimeTypes = new Set();
30
+ const locations = new Map();
31
+ const hostnames = new Set();
32
+ const dataTypes = new Set();
33
+ let encryptedCount = 0;
34
+ let unencryptedCount = 0;
35
+ let totalPackets = 0;
36
+
37
+ if (!capturedPackets || !capturedPackets["Host"]) return null;
38
+
39
+ for (const host of Object.keys(capturedPackets["Host"])) {
40
+ const normalizedHostKey = normalizeStatsTextValue(host);
41
+ if (normalizedHostKey) hosts.add(normalizedHostKey);
42
+ const packets = capturedPackets["Host"][host];
43
+ if (!Array.isArray(packets)) continue;
44
+
45
+ for (const pkt of packets) {
46
+ totalPackets++;
47
+ const pi = pkt?.["Packet Info"];
48
+ const ei = pkt?.["Extra Info"];
49
+ if (!pi || !ei) continue;
50
+
51
+ const tp = normalizeStatsTextValue(pi["Protocol"]);
52
+ if (tp) transportProtocols.add(tp);
53
+
54
+ const srcIp = normalizeStatsTextValue(pi?.["IP"]?.["Source IP"]);
55
+ const dstIp = normalizeStatsTextValue(pi?.["IP"]?.["Destination IP"]);
56
+ if (srcIp) hosts.add(srcIp);
57
+ if (dstIp) hosts.add(dstIp);
58
+
59
+ const ef = pi?.["Ethernet Frame"];
60
+ if (ef) {
61
+ const srcVendor = normalizeStatsTextValue(ef["MAC Source Vendor"]);
62
+ const dstVendor = normalizeStatsTextValue(ef["MAC Destination Vendor"]);
63
+ if (srcVendor) macVendors.add(srcVendor);
64
+ if (dstVendor) macVendors.add(dstVendor);
65
+ }
66
+
67
+ const netData = ei?.["Traits"]?.["Network Data"];
68
+ if (netData) {
69
+ const protoName = normalizeStatsTextValue(
70
+ netData["Port Protocol"] ?? netData["Port Protcol"],
71
+ );
72
+ if (protoName && protoName !== "Unknown") protocols.add(protoName);
73
+
74
+ const tpData = tp ? pi[tp] : null;
75
+ if (tpData) {
76
+ const srcPort = normalizeStatsPortValue(tpData["Source port"]);
77
+ const dstPort = normalizeStatsPortValue(tpData["Destination port"]);
78
+ if (srcPort !== null) ports.add(srcPort);
79
+ if (dstPort !== null) ports.add(dstPort);
80
+ }
81
+
82
+ const hn = netData?.["Hostnames"]?.["Hostnames"];
83
+ if (Array.isArray(hn)) {
84
+ hn.forEach((h) => {
85
+ const normalizedHostname = normalizeStatsTextValue(h);
86
+ if (normalizedHostname) hostnames.add(normalizedHostname);
87
+ });
88
+ }
89
+
90
+ for (const side of ["Source IP", "Destination IP"]) {
91
+ const loc = netData?.[side]?.["Location"];
92
+ const city = normalizeStatsTextValue(loc?.["City"]);
93
+ const country = normalizeStatsTextValue(loc?.["Country"]);
94
+ if (city && country) {
95
+ const key = `${city}, ${country}`;
96
+ locations.set(key, (locations.get(key) || 0) + 1);
97
+ }
98
+ }
99
+ }
100
+
101
+ const mimeType = normalizeStatsTextValue(ei?.["MIME Type"]);
102
+ if (mimeType) mimeTypes.add(mimeType);
103
+
104
+ const dt = ei?.["Data Types"];
105
+ if (Array.isArray(dt)) {
106
+ dt.forEach((d) => {
107
+ const normalizedDataType = normalizeStatsTextValue(d, {
108
+ stripNonPrintable: true,
109
+ });
110
+ if (normalizedDataType) dataTypes.add(normalizedDataType);
111
+ });
112
+ }
113
+
114
+ const encData = ei?.["Traits"]?.["Server Info"]?.["Encryption Data"];
115
+ if (!encData || encData === "N/A") {
116
+ unencryptedCount++;
117
+ } else {
118
+ encryptedCount++;
119
+ }
120
+ }
121
+ }
122
+
123
+ return {
124
+ protocols: [...protocols].sort(),
125
+ transportProtocols: [...transportProtocols].sort(),
126
+ hosts: [...hosts].sort(),
127
+ ports: [...ports].sort((a, b) => a - b),
128
+ macVendors: [...macVendors].filter((v) => v !== "N/A").sort(),
129
+ mimeTypes: [...mimeTypes].sort(),
130
+ locations: [...locations.entries()].sort((a, b) => b[1] - a[1]),
131
+ hostnames: [...hostnames].sort(),
132
+ dataTypes: [...dataTypes].sort(),
133
+ encryptedCount,
134
+ unencryptedCount,
135
+ totalPackets,
136
+ };
137
+ }
138
+
139
+ function makeStatsSection({ documentRef, title, items, queryBuilder, onQuery }) {
140
+ if (!items || items.length === 0) return null;
141
+ const normalizedItems = Array.from(
142
+ new Set(
143
+ items.filter((item) => {
144
+ if (item === null || item === undefined) return false;
145
+ if (typeof item !== "string") return true;
146
+ return normalizeStatsTextValue(item) !== null;
147
+ }),
148
+ ),
149
+ );
150
+ if (normalizedItems.length === 0) return null;
151
+
152
+ const section = documentRef.createElement("div");
153
+ section.className = "stats-section";
154
+
155
+ const heading = documentRef.createElement("div");
156
+ heading.className = "stats-section-title";
157
+ heading.textContent = title;
158
+ section.appendChild(heading);
159
+
160
+ const tagList = documentRef.createElement("div");
161
+ tagList.className = "stats-tag-list";
162
+
163
+ normalizedItems.forEach((item) => {
164
+ const tag = documentRef.createElement("span");
165
+ tag.className = "stats-tag";
166
+ tag.textContent = item;
167
+ tag.title = "Click to filter packets by this value";
168
+ if (queryBuilder) {
169
+ tag.addEventListener("click", () => {
170
+ const query = queryBuilder(item);
171
+ if (query && typeof onQuery === "function") {
172
+ onQuery(query);
173
+ }
174
+ });
175
+ }
176
+ tagList.appendChild(tag);
177
+ });
178
+
179
+ section.appendChild(tagList);
180
+ return section;
181
+ }
182
+
183
+ function createStatsPanel(options) {
184
+ const {
185
+ documentRef,
186
+ statusUpdate,
187
+ writeLogEntry,
188
+ setActiveMainTab,
189
+ mainTabStats,
190
+ getJsonCapture,
191
+ getCapturedPackets,
192
+ filterInputEl,
193
+ syncFilterHighlight,
194
+ runFilterQuery,
195
+ getFilteredPackets,
196
+ setPacketsForHost,
197
+ } = options;
198
+
199
+ function applyStatsQuery(query) {
200
+ filterInputEl.value = query;
201
+ syncFilterHighlight();
202
+ writeLogEntry(`Stats tag clicked query="${query}"`);
203
+ runFilterQuery(query);
204
+ const filteredPackets = getFilteredPackets();
205
+ if (Array.isArray(filteredPackets) && filteredPackets.length > 0) {
206
+ setPacketsForHost(filteredPackets);
207
+ }
208
+ }
209
+
210
+ function showStats() {
211
+ setActiveMainTab(mainTabStats);
212
+ if (getJsonCapture() === "") {
213
+ statusUpdate("Status: No JSON file loaded, please upload a file first");
214
+ return;
215
+ }
216
+ statusUpdate("Status: Displaying capture statistics");
217
+ writeLogEntry("User opened capture stats view");
218
+
219
+ documentRef.getElementById("packetInfoPane").style.display = "none";
220
+ documentRef.getElementById("packetPayloadPane").style.display = "none";
221
+ documentRef.getElementById("prev-btn").style.display = "none";
222
+ documentRef.getElementById("next-btn").style.display = "none";
223
+ documentRef.getElementById("summary_box").style.display = "none";
224
+ documentRef.getElementById("list_box").style.display = "none";
225
+ documentRef.getElementById("notes_box").style.display = "none";
226
+ documentRef.getElementById("data_tools_box").style.display = "none";
227
+ documentRef.getElementById("crypt_box").style.display = "none";
228
+ documentRef.getElementById("keystore_box").style.display = "none";
229
+ documentRef.getElementById("stats_box").style.display = "block";
230
+ documentRef.getElementById("rightside").style.display = "none";
231
+
232
+ const content = documentRef.getElementById("stats_content");
233
+ content.replaceChildren();
234
+
235
+ const stats = buildCaptureStats(getCapturedPackets());
236
+ if (!stats) {
237
+ content.textContent = "No packet data available.";
238
+ return;
239
+ }
240
+
241
+ const overview = documentRef.createElement("div");
242
+ overview.className = "stats-section";
243
+ const ovHead = documentRef.createElement("div");
244
+ ovHead.className = "stats-section-title";
245
+ ovHead.textContent = "Capture Overview";
246
+ overview.appendChild(ovHead);
247
+ [
248
+ `Total Packets: ${stats.totalPackets}`,
249
+ `Unique Hosts Targeted: ${stats.hosts.length}`,
250
+ `Encrypted Packets: ${stats.encryptedCount}`,
251
+ `Unencrypted Packets: ${stats.unencryptedCount}`,
252
+ `Unique Protocols: ${stats.protocols.length}`,
253
+ `Unique Locations: ${stats.locations.length}`,
254
+ ].forEach((line) => {
255
+ const kv = documentRef.createElement("div");
256
+ kv.className = "stats-kv";
257
+ kv.textContent = line;
258
+ overview.appendChild(kv);
259
+ });
260
+ content.appendChild(overview);
261
+
262
+ const protoSec = makeStatsSection({
263
+ documentRef,
264
+ title: "Application Protocols",
265
+ items: stats.protocols,
266
+ queryBuilder: (v) => `tcp.proto: ${v.toLowerCase()}`,
267
+ onQuery: applyStatsQuery,
268
+ });
269
+ if (protoSec) content.appendChild(protoSec);
270
+
271
+ const tpSec = makeStatsSection({
272
+ documentRef,
273
+ title: "Transport Protocols",
274
+ items: stats.transportProtocols,
275
+ queryBuilder: (v) => `wire.proto: ${v.toLowerCase()}`,
276
+ onQuery: applyStatsQuery,
277
+ });
278
+ if (tpSec) content.appendChild(tpSec);
279
+
280
+ const hostSec = makeStatsSection({
281
+ documentRef,
282
+ title: "All Hosts Addressed",
283
+ items: stats.hosts,
284
+ queryBuilder: (v) => `ip.src.addr: ${v} || ip.dst.addr: ${v}`,
285
+ onQuery: applyStatsQuery,
286
+ });
287
+ if (hostSec) content.appendChild(hostSec);
288
+
289
+ const hnSec = makeStatsSection({
290
+ documentRef,
291
+ title: "Hostnames (DNS)",
292
+ items: stats.hostnames,
293
+ queryBuilder: (v) => `dns.qname: ${v}`,
294
+ onQuery: applyStatsQuery,
295
+ });
296
+ if (hnSec) content.appendChild(hnSec);
297
+
298
+ if (stats.locations.length > 0) {
299
+ const locItems = stats.locations.map(([place, count]) => `${place} (${count})`);
300
+ const locSec = makeStatsSection({
301
+ documentRef,
302
+ title: "Physical Locations",
303
+ items: locItems,
304
+ });
305
+ if (locSec) content.appendChild(locSec);
306
+ }
307
+
308
+ const portSec = makeStatsSection({
309
+ documentRef,
310
+ title: "Ports Seen",
311
+ items: stats.ports.map(String),
312
+ queryBuilder: (v) => `tcp.src.port: ${v} || tcp.dst.port: ${v}`,
313
+ onQuery: applyStatsQuery,
314
+ });
315
+ if (portSec) content.appendChild(portSec);
316
+
317
+ const macSec = makeStatsSection({
318
+ documentRef,
319
+ title: "MAC Vendors",
320
+ items: stats.macVendors,
321
+ queryBuilder: (v) => `eth.src.vendor: ${v}`,
322
+ onQuery: applyStatsQuery,
323
+ });
324
+ if (macSec) content.appendChild(macSec);
325
+
326
+ const mimeSec = makeStatsSection({
327
+ documentRef,
328
+ title: "MIME Types",
329
+ items: stats.mimeTypes,
330
+ queryBuilder: (v) => `mime.type: ${v}`,
331
+ onQuery: applyStatsQuery,
332
+ });
333
+ if (mimeSec) content.appendChild(mimeSec);
334
+
335
+ const dtSec = makeStatsSection({
336
+ documentRef,
337
+ title: "Data Types",
338
+ items: stats.dataTypes,
339
+ });
340
+ if (dtSec) content.appendChild(dtSec);
341
+ }
342
+
343
+ return {
344
+ showStats,
345
+ };
346
+ }
347
+
348
+ module.exports = {
349
+ id: "stats",
350
+ createStatsPanel,
351
+ };
@@ -0,0 +1,63 @@
1
+ const SUMMARY_LOADING_MARKUP =
2
+ '<span id="loaderdots" class="loading" role="status" aria-live="polite">Loading</span>';
3
+
4
+ function createSummaryPanel({
5
+ documentRef,
6
+ getJsonCapture,
7
+ getFinalSummary,
8
+ setActiveMainTab,
9
+ mainTabSummary,
10
+ statusUpdate,
11
+ fileLoaded,
12
+ }) {
13
+ function showSummary() {
14
+ setActiveMainTab(mainTabSummary);
15
+ statusUpdate("Status: Displaying capture analysis summary");
16
+ const jsonCapture = getJsonCapture();
17
+ if (jsonCapture === "" || jsonCapture === null || jsonCapture === undefined) {
18
+ statusUpdate("Status: No JSON file loaded, please upload a file first");
19
+ return;
20
+ }
21
+
22
+ documentRef.getElementById("packetInfoPane").style.display = "none";
23
+ documentRef.getElementById("packetPayloadPane").style.display = "none";
24
+ documentRef.getElementById("prev-btn").style.display = "none";
25
+ documentRef.getElementById("next-btn").style.display = "none";
26
+ documentRef.getElementById("stats_box").style.display = "none";
27
+ documentRef.getElementById("data_tools_box").style.display = "none";
28
+ documentRef.getElementById("crypt_box").style.display = "none";
29
+ documentRef.getElementById("keystore_box").style.display = "none";
30
+ documentRef.getElementById("list_box").style.display = "none";
31
+ documentRef.getElementById("notes_box").style.display = "none";
32
+ documentRef.getElementById("rightside").style.display = "none";
33
+ documentRef.getElementById("summary_content").textContent =
34
+ getFinalSummary() || "No LLM summary available.";
35
+ documentRef.getElementById("summary_box").style.display = "block";
36
+ fileLoaded(true);
37
+ }
38
+
39
+ function showSummaryLoading() {
40
+ const summaryContentEl = documentRef.getElementById("summary_content");
41
+ if (!summaryContentEl) return;
42
+ summaryContentEl.innerHTML = SUMMARY_LOADING_MARKUP;
43
+ }
44
+
45
+ function clearSummaryContent() {
46
+ const summaryContentEl = documentRef.getElementById("summary_content");
47
+ if (!summaryContentEl) return;
48
+ summaryContentEl.textContent = "";
49
+ }
50
+
51
+ const summaryBtnEl = documentRef.getElementById("summary-btn");
52
+ if (summaryBtnEl) {
53
+ summaryBtnEl.addEventListener("click", showSummary);
54
+ }
55
+
56
+ return {
57
+ showSummary,
58
+ showSummaryLoading,
59
+ clearSummaryContent,
60
+ };
61
+ }
62
+
63
+ module.exports = { createSummaryPanel };
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ /**
3
+ * This is the main entry point for your application, it's the first file
4
+ * that runs in the main process.
5
+ */
6
+ entry: './src/main.js',
7
+ // Put your normal webpack config below here
8
+ module: {
9
+ rules: require('./webpack.rules'),
10
+ },
11
+ };
@@ -0,0 +1,13 @@
1
+ const path = require('path');
2
+ const CopyPlugin = require('copy-webpack-plugin');
3
+
4
+ module.exports = [
5
+ new CopyPlugin({
6
+ patterns: [
7
+ {
8
+ from: path.resolve(__dirname, 'src/assets'),
9
+ to: 'assets',
10
+ },
11
+ ],
12
+ }),
13
+ ];
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ // ...existing config
3
+ target: 'electron-preload',
4
+ externals: {
5
+ fs: 'commonjs2 fs',
6
+ },
7
+ };
@@ -0,0 +1,30 @@
1
+ const rules = require('./webpack.rules');
2
+ const plugins = require('./webpack.plugins');
3
+ const webpack = require('webpack');
4
+
5
+ rules.push({
6
+ test: /\.css$/,
7
+ use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
8
+ });
9
+
10
+ module.exports = {
11
+ // Put your normal webpack config below here
12
+ module: {
13
+ rules,
14
+ },
15
+ resolve: {
16
+ fallback: {
17
+ buffer: require.resolve('buffer/'),
18
+ process: require.resolve('process/browser'),
19
+ stream: require.resolve('stream-browserify'),
20
+ vm: require.resolve('vm-browserify'),
21
+ },
22
+ },
23
+ plugins: [
24
+ ...plugins,
25
+ new webpack.ProvidePlugin({
26
+ Buffer: ['buffer', 'Buffer'],
27
+ process: 'process/browser',
28
+ }),
29
+ ],
30
+ };
@@ -0,0 +1,35 @@
1
+ module.exports = [
2
+ // Add support for native node modules
3
+ {
4
+ // We're specifying native_modules in the test because the asset relocator loader generates a
5
+ // "fake" .node file which is really a cjs file.
6
+ test: /native_modules[/\\].+\.node$/,
7
+ use: 'node-loader',
8
+ },
9
+ {
10
+ test: /[/\\]node_modules[/\\].+\.(m?js|node)$/,
11
+ parser: { amd: false },
12
+ use: {
13
+ loader: '@vercel/webpack-asset-relocator-loader',
14
+ options: {
15
+ outputAssetBase: 'native_modules',
16
+ },
17
+ },
18
+ },
19
+ // Put your webpack loader rules in this array. This is where you would put
20
+ // your ts-loader configuration for instance:
21
+ /**
22
+ * Typescript Example:
23
+ *
24
+ * {
25
+ * test: /\.tsx?$/,
26
+ * exclude: /(node_modules|.webpack)/,
27
+ * loaders: [{
28
+ * loader: 'ts-loader',
29
+ * options: {
30
+ * transpileOnly: true
31
+ * }
32
+ * }]
33
+ * }
34
+ */
35
+ ];