Release Notes
v4.0.0 - 2026-10-25
Type: major
Anything a user can get hold of — a pcap, a saved session, a document, an archive — can now be brought into the running session from the sidebar and becomes an entity: a first-class data object that the whole app can list, decode, mask and compare. Entities appear in a new Entities subtab of the Artifact Store, each with a per-entity masking profile that governs both what the app shows and what Save/Export writes.
✨ Features
- Bring anything into a session — a Bring in… button under
Sessions Library, plus the whole left rail as a drag-and-drop target. A file
is routed by its magic bytes, not its name: a pcap/pcapng/session is parsed and
merged as an additional source (the same path Reprocess All Session
PCAPs uses), while any other byte sequence is hashed, run through the decoder
registry and registered as a file entity. Dropped paths are resolved with
Electron’s
webUtils, so a dropped.pcapreaches the backend instead of being read as text. - Entities workspace (Artifact Store → Entities) — a card per data
object showing kind, size, SHA-256, a decoder verdict (
STRUCTURED · httpvsBLOB) and its mask state. Double-click — or a per-card context menu — to go inside: a decoded blob opens in Host Data, raw bytes open in Conv → Decodes, and a capture entity opens List filtered to its source. Compare left / right diffs two entities with the same pure engines the Compare workspace uses (byte diff, byte metrics, SSDEEP-style similarity, decoded-field diff). - Adaptive analysis depth — every entity becomes a synthetic packet record
(
capture.entityId,Raw data, and a decodedENTITYsubtree only when a decoder actually claimed the bytes), so List, Stats, Host Data, Compare, Hashes, search and the LLM context all work on a document exactly as they work on a packet. Unstructured bytes stay a blob rather than advertising an empty field tree. - Loading overlays cover the blocking work they wrap — the “Indexing
capture…” overlay stays up for the whole capture-load sequence (the initial
load and its incremental continuation) instead of being dismissed while the
renderer is still blocked, and following a stream into Conv shows a
“Preparing stream…” overlay that a
finallyclears, so a failed hand-off can never leave the window stuck. Incremental callers keep the old dismiss behaviour only when they explicitly suppress the overlay.
🪆 Recursive extraction
- Every object is taken apart as far as the decoders can take it. An import is no longer one card. A ZIP of pcaps becomes a ZIP card whose children are captures, whose payload streams fan out beneath them; a gzip inside a tar inside a session becomes a five-level tree, each level addressable, maskable and comparable like anything else in the session. Archives, single-stream compression and nested captures are all followed; anything the decoders cannot open further is a leaf that still decodes in Conv.
- The catalog is searchable with the ordinary filter. Entity descriptors are
filterable fields —
entity-type: image,depth: 2,extracted-from: <entity id>,extraction-kind: archive-member,entity-file: report.pdf,entity-kind: derived— so the Entities grid needs no second search box, and a query works the same on List. - Bounded, and it says when it stopped. Extraction stops at four levels, 50 members per container, 400 objects per import, 64 MiB per member and 512 MiB total. A member too large to carry is still catalogued by name and size, because “there is a 2 GB file in here” is itself the finding. Nothing is dropped silently: the activity log records which limit was hit and what failed to open.
- A capture source appears in Entities. A pcap opened through the ordinary loader — File → Open…, a merged session, a session restore — now gets a Capture card keyed on the source id its packets actually carry, so masking, export redaction and show me this capture all target something real.
🔒 Masking
- Per-entity redaction profiles —
none,metadata(IPs, MACs, hostnames, credentials) andfull(adds ports, URLs, file names, raw bytes), with a per-field policy ofdeterministic(stable HMAC token, so the same address correlates across entities),pseudonym(host-01, reversible only with a mapping the export can opt into) orirreversible. - One control, two surfaces. An entity’s mask is the existing per-source
visibleflag, so Stats → Capture Sources and the Entities panel cannot disagree; the save/export transform runs at the singlebuildSessionFilePayloadchokepoint, so a masked entity is absent from List, Stats, Compare, the LLM context and from every saved session, exported file and autosave. Redacted tokens keep their shape (an IP still parses as an IP), host-map keys are redacted too, and identity fields (capture.entityId) survive so a manifest can still name what was masked. - Licensing — bringing a file in, decoding it, opening it and listing
entities are free. Masking profiles, entity comparison and batch analysis
require a Professional/Enterprise licence via the new
hasEntityAdvancedAccesspredicate, so the renderer controls and the AI actions read one rule.
🗂 File-first sessions
- Start with a file, not a capture — a session can now begin with a document,
an archive or a database dump. The file lane reuses the same loader a pcap
uses (
processCaptureData), so the imported entity is indexed by the capture store, the host list is populated and the session is genuinely loaded rather than holding an unreachable overlay packet. - Capture-dependent tabs are disabled until a timeline exists — List, Stats, Comp and Crypt need a packet sequence, so they are disabled with an explanatory tooltip while the session holds no capture. Analysis, Host Data, Conv, Artifacts, Notes, Settings and Help remain usable, and the Artifacts tab always opens because that is where Bring in… lives.
- Importing a file never replaces a loaded capture — the entity packets are merged into the existing capture rather than replacing it, and a subsequent capture load releases the store commit so an entity imported first cannot vanish.
🖱 One context menu
- The Entities workspace no longer opens a second menu — right-clicking an entity card opened a panel-local menu at the same time as the application’s context menu. The card now only selects itself, and the single shared menu shows an Entity… section (Open, Decode in Conv, Hashes, Compare left/right/pair, Masking ▸ mask & profiles, Export, Remove) in place of the packet/file families when an entity card is the target.
🧩 One grid for everything
- Every artifact is an entity — carved files, downloaded files, Conv extractions and decompressions now appear automatically in the Entities subtab, pointing back at their Artifact Store record. Nothing has to be imported twice.
- Streams fan out under their capture — each network stream becomes a child entity nested under the capture it came from, badged with the decoder the guess cascade ranked most probable for that stream.
- Per-type icons — captures, streams, archives, images, documents, certificates, keys, audio, video, databases, executables, text and blobs each get their own glyph and colour, so the grid is scannable without reading labels.
- A new session opens the workspace, not a file picker — New Session clears the session and lands on the Entities subtab, where Bring in… and the sidebar drop target accept a pcap or any file.
- New entity types — MAC addresses, protocol layers, protocols, credentials
and URLs / URIs each get their own glyph (
⌘,▤,⌁,⚿,↗; URL and URI share the↗glyph). A derived entity’s declared type wins over content sniffing, and file identity beats the transport that carried it — an HTTP-carvedpayload.zipis still an archive, not an HTTP stream. - Entity cards render larger, with more at a glance — cards are ~30 % bigger and show live child counts, application protocols nest under their transport card, the icon sits above the label, and the open buttons follow the theme.
⚡ Performance
- A file-only session no longer runs packet analysis over a document. Moving
between tabs re-ran five anomaly detectors, the stats aggregator, the top-talker
pass and the carvable-stream scanner over the synthetic wrapper packet that exists
only so a file can be listed. All of that work now sinks through a single
isPacketSequenceAvailablecheck (and a per-packet skip in the mixed case), so a single PNG or ZIP costs nothing to browse. Entity work — name, size, hash, type, decoders — is never skipped.
🐞 Fixes
- A file-only session no longer locks up on tab switches — the panels treated
getJsonCapture() === ""as “nothing loaded”, but a capture-store-backed session sets that to the sentinel"[lazy-capture-store]". A file-first session therefore passed the guard and rendered a synthetic entity packet as if it were captured traffic. Panels now askhasAnalysableSessionData(), and the packet-only views say plainly that they need a capture instead of drawing a fake row. - Importing a second pcap no longer produces a card that shows nothing. The
merge re-stamps every packet with a source id it derives itself, so the new
source’s entity was keyed on a derived
entity:<id>that no packet carried. Stored source ids are now validated, every packet’s source id is rewritten to the claimedentity:<id>(claimSourceId), and per-source descriptors are stamped at merge time (buildSourceId/buildAnnotationState), so the card, its list and masking all resolve the same packets. - Entity packets survive a capture-store round-trip.
buildPacketStubnow resolves every payload-casing variant (Raw data/raw.data,Payload/payload,Hex Encoded,payload.hex,payload.hex.encoded, explicitpayload.len/Payload Length), so a derived packet written under one casing shape is read back under another instead of coming out empty. - The capture-only tabs are actually disabled for a file-only session — the
synthetic-packet marker is stripped when a packet is round-tripped through the
capture store, so the gate had been classifying a committed file entity as a real
capture. Detection now uses the
packet.protolabel, which survives the store. - Analysis is a real landing tab —
resolveDefaultLandingTabcould return Analysis and haveopenDefaultLandingTabfall through to Stats, opening the very workspace it had just refused. - New Session no longer forces the file picker.
- An entity no longer appears twice in the Entities grid. An imported file and the
artifact record it persists were being adopted as two separate entities, because the
importer never recorded which store record it created. Import now sets
bytesRefup front, and adoption matches on either the store id or the content id, backfilling the link so later passes are cheap. - Two different files with the same name stay two entities. Entity identity falls back to name + size when a record has no bytes, which collapsed distinct same-named files of equal length; the fallback now mixes in the record ordinal and import time.
- Bring in… actually opens a pcap into an empty session. The capture lane
returned
{ mode: "load" }for the first capture and nothing consumed it, so picking a pcap into a session that held no capture silently did nothing. The lane now calls the ordinary loader (runSnitch), which parses and indexes. - A large pcap is no longer refused for being large. The file picker ships the file’s bytes to the renderer, and the renderer refused anything over the 32 MiB entity import limit — a ceiling that exists for documents carried inside the session and has nothing to do with captures, which the backend reads from a path. A capture now goes straight to the path-based lane without its bytes crossing the IPC boundary.
- A capture added to a merged session no longer produces a card that shows
nothing. The merge re-stamps every packet with a source id it derives itself, but
the entity was keyed on a derived
entity:<id>; masking it, exporting it masked, or asking to see its packets all matched zero packets. The entity now adopts the id the merge actually used. - An archive member keeps its own icon. A derived entity with no decoder (an extracted member) was classified as a stream, so every PNG and pcap pulled out of a ZIP got a stream glyph. Only a real stream has a stream key; everything else is classified by its own name, MIME type and content.
- A member too large to carry is still findable. It was catalogued but received no packet, so the filter could not see it. Genuine stream children still get no synthetic packet — their content is the capture’s packets — but a described member now does.
🤖 AI
- Six new actions —
entity.list,entity.import,entity.open,entity.compare,entity.set-redaction,entity.set-mask— registered inconfig/action-policy.jsonand documented from their own descriptors.entity.listreturns identity and analysis metadata only: never file bytes, never unmasked secrets.
🧪 Tests
tests/entity_model.test.js(identity, tolerant normalization, synthetic packet contract),tests/redaction_profiles.test.js(all profiles × modes, determinism, byte-level transform),tests/entity_mask_chokepoint.test.js(the two-surface invariant: masked means absent from the view and the export),tests/entity_ingest.test.js(magic routing, size ceiling, merge semantics, no-path refusal),tests/entities_panel.test.js(card projection, subtab visibility, Pro gating),tests/entity_actions.test.js(policy parity, the free/paid split, the no-secrets projection).
🗄 Catalog server
- Support mail can be deleted from the admin portal —
POST /admin/api/mail/<id>/deleteremoves a message viadelete_support_messageand records amail_deleteaudit entry (admin id, admin email, message id) on success. - Support-mail bodies may now show remote images. The rendered-body CSP
(
SUPPORT_BODY_CSP) openedimg-srctohttp:andhttps:as a documented tracking-pixel trade-off, while the iframe stayssandbox=""withreferrerpolicy="no-referrer", so no script, form, popup or navigation can escape an email.
v3.4.0 - 2026-10-18
Type: minor
The catalog server can now read, store and answer mail sent to
support@packetsnitch.com. Because that address is an alias into a human mailbox, a read-only IMAP poller copies only the messages addressed to the alias into the catalog database, ties each one to the customer and install UUIDs it belongs to, and surfaces it in a new Mail tab in/admin/with reply and compose on the branded support template.
✨ Features
- Support mailbox in the admin portal — a Mail tab lists every message
sent to
support@packetsnitch.com, filterable by linked / unlinked and searchable over subject, sender, snippet and customer. The detail pane shows the sanitized body, attachments, the customer link and the rest of the conversation. Replies (threaded withIn-Reply-To/References) and brand-new messages are sent from the support address through the existing local SMTP relay, wrapped in the same purple_render_email_shelltemplate every other PacketSnitch email uses. Read is viewer+, reply/compose/link is operator+, forcing a poll is super_admin; every write lands in the audit log. - Read-only IMAP ingest — a daemon poller (started next to the Paddle
poller, stopped by the same event) fetches with
BODY.PEEK[]so it never marks mail as read, and never moves or deletes anything. The first pass backfills the folder (support_mail_backfill_days: 0) and later passes are incremental from a persisted UID high-water mark. Filtering is on the recipient headers (Delivered-To/X-Original-To/Envelope-To/To/Cc), because the mailbox also holds the owner’s personal mail. Off by default (support_mail_enabled: false) so a host without a mailserver does not log failures. - Customer and install linkage — an inbound message auto-links to the
customer whose
installs.customer_emailmatches its sender exactly (any verification state), expanding to every install UUID that customer owns in the newsupport_message_installstable. No match leaves it unlinked, an explicit state an operator can resolve by hand. Sent mail is stored as an outbound row so the tab shows both sides of a conversation — necessary because the alias forwards to a different account, so our replies never come back through the polled mailbox. - Hostile mail HTML is neutralised at ingest —
sanitize_email_htmldrops script-bearing elements with their contents, keeps a presentation-only attribute/CSS allow-list, restricts URLs tohttp/https/mailto/teland rewritescid:references to the authenticated attachment route. The portal renders the result in a<iframe sandbox="">as a second boundary. Attachments are written tosupport_attachments_dir(hashed fan-out, safe basenames, 25 MiB/file and 50 MiB/message caps) rather than into the database, and are served only throughGET /admin/api/mail/<id>/attachment/<attId>with a store-root re-check. - Schema v8 —
support_messages,support_message_installsandsupport_message_attachmentsadded to bothSCHEMAandPOSTGRES_SCHEMA(parity-tested), with the new tables registered inCATALOG_TABLES,CATALOG_IDENTITY_COLUMNSand the PostgreSQLlastrowidbridge. The installer creates/var/lib/ps-catalog/support-attachmentsand the unit file grants it throughReadWritePaths. - Inline images in support mail now display — a body’s
<img src="cid:…">references were rewritten to an attachment URL that (a) did not match the route’s shape and (b) could never have loaded anyway: the body is rendered in<iframe sandbox="">, an opaque origin, so the request would not carry theSameSite=Strictadmin cookie. They are now resolved todata:URIs from the catalog’s own attachment store, so the rendered document is fully self-contained and fetches nothing at all. Inlining is limited to PNG/JPEG/GIF/WebP/BMP/ICO parts under a 5 MiB total cap; SVG is excluded because it is a scriptable document, and anything else falls back to the downloadable attachment via?cid=. Every rendered body also carries a lockeddefault-src 'none'CSP (img-src data:only), and the iframe keepssandbox=""with noallow-scripts/allow-same-origin, so no script, form, popup or top-navigation can run from an email.body_html_raw(schema v9) stores the pre-sanitize markup so re-rendering costs no IMAP round-trip; it is never sent to a browser. - Mail HTML bodies render again — the sanitizer treated a leading
<!DOCTYPE html>as text (so it appeared literally at the top of every message) and pushed void elements like<meta>and<link>onto its dropped-element stack, which HTML never closes — swallowing the rest of the body. Declarations are now removed rather than escaped, void elements never arm the stack, and<head>/<title>are dropped so the subject does not reappear in the body. The tab also declines to render an HTML body that sanitizes down to nothing, falling back to the plain-text part. Because mail ingested before the fix stored only the escaped declaration,POST /admin/api/mail/repair(super_admin, plus a Repair bodies button and a count in the Mail tab’s status line) re-reads those messages from the mailbox by their stored UID and rewrites the body in place. - New tests —
tests/test_catalog_support_mail.pycovers recipient filtering (including personal mail in the same mailbox), thread derivation, the sanitizer, attachment caps, linking and unlink round-trips, idempotent ingest, the poller’s incremental high-water mark, outbound threading, and the HTTP role gates;tests/catalog_admin_mail_ui.test.jscovers the Mail tab’s rendering, role gating and request payloads.
v3.3.1 - 2026-10-16
Type: patch
Ask PacketSnitch can hand a payload it pulled out of a stream back to Conv as a real file, drive the Stats timeline from a chat request, and follow a multi-step request through to the end — with a new Chat noise level slider deciding how much of that it narrates. The catalog server reports Paddle purchases to GA4 and sends branded purple portal emails, and release notes are now readable in Settings → About. This cut also carries the Developer “Free mode” simulation, the PostgreSQL catalog backend, and the investigation playbook that the previous release did not pick up.
✨ Features
-
Stream-aware payload reload (“Reload in Conv”) — eight of the stream decoders (HTTP, FTP, IMAP, SMTP, POP3, NNTP, TFTP, WebSocket) can now hand the whole data chunk they carry back to the Conv tab as a one-click Reload in Conv button instead of stopping at a per-packet view of it. A file body, an RFC 5322 message, or an IMAP literal spans many packets, so a decoder whose only input is a single packet cannot produce it; the new
extractStreamPayloadshook (insrc/ui/decoders/protocols/payloads.js) is how a decoder asks Conv for the reassembled conversation. Conv injects the accessors, the decoder returns{ payloads: [...] }or nothing, and the extracted bytes load into Conv as their own file — so a ZIP opens in the archive decoder instead of an opaque blob, and the declared type becomes the decoder hint. A payload with no bytes is not a payload, so a decoder that finds nothing returnsnulland no button is rendered. Each payload is bounded (16 MiB, flaggedtruncatedrather than silently cut; 64 entries per stream). Covered bytests/decoder_stream_payloads.test.jsandtests/decoder_stream_payloads_ui.test.js. -
Encapsulated-data decoding, wired end to end — the same hook was taught to the individual protocol decoders, so content pulled out of an HTTP body, an FTP data connection, an IMAP literal, an SMTP/POP3/NNTP message, or a TFTP transfer arrives in Conv with the right filename and extension hint and decodes as the file it actually is. This closes the “encapsulated content” gap that previously left an analyst with raw hex and a note that the bytes might be a file.
-
Play and control the Stats timeline from chat — Ask PacketSnitch can drive the Stats → Map timeline animation the same way the on-screen Play/Reset buttons and speed slider do. The new
stats.timeline-controlaction acceptsplay,pause,toggle,reset,seek(timeMs),speed, andstatus; it opens the Map subtab itself when it is not rendered and re-reads the live timeline handle on every call, so a stale handle can never drive a detached canvas. An autonomous step cannot answer the timeline’s “set network anchors first” dialog, so the AI entry point passes an auto-confirm flag and takes the documented corner-pin fallback, while the user-facing button still shows the dialog. Covered bytests/stats_timeline_control.test.js. -
Chat noise level — a new Chat noise level slider (Settings → LLM, under Rat Flocking;
settings.llm.chatVerbosity, 0–3) decides how much play-by-play Ask PacketSnitch posts. Level 0 is quiet (the final answer only); level 1 (default) adds a line for a notable route and for a multi-step plan starting or finishing; level 2 adds one line per executed step; level 3 is diagnostic (every route, plus recovery/regroup, reflection, and follow-through bookkeeping). The levels are strict supersets, an unknown notice kind is never silenced (fail-open), and a silenced line skips both the bubble and the pacing hold so a quiet run is not slower than a chatty one. Terminal outcomes — the answer itself, and anything the user has to answer or confirm — stay ungated. Every progress line routes through one predicate,shouldPostChatNoticeinsrc/ui/ask-shortcuts.js. -
A multi-step request is never short-circuited —
isMultiStepShapedRequest(src/ui/ask-shortcuts.js) detects sequencing connectors (“then”, “after that”), enumerated lists, and comma-joined imperatives, and gates every chat shortcut at a single choke point, so a request like “load X into Conv, then unzip it and decode Y” always reaches the classifier that plans and orders every step instead of firing the first matching shortcut and dropping the rest. The flock follow-up is guarded at its head the same way. Covered bytests/ask_shortcuts.test.js. -
Follow-through on multi-step requests — the router prompt now counts a request’s distinct goals and emits an optional
multiStephint, and after the planned steps drain a single follow-through pass (askFollowThroughCompletion) asks whether every goal was actually satisfied.complete:falsewith corrective steps folds them back in for one more run;complete:falsewith no steps is an honest give-up; the pass is capped at one corrective plan. The common map-focus dead end is handled on three layers — the router prompt resolves a host role before guessing an IP, the dead-end recovery prompt warns against repeating a guessed IP, and the follow-through pass supplies the realavailableStatsLocationslist. Disable withsettings.llm.followThroughEnabled; the adaptive between-step reflection stays independent undersettings.llm.planStepReflectEnabled. Pure helpers are unit-tested intests/ask_packet_snitch_adaptive_plan.test.js. -
Every capability is documented in one place — the Ask PacketSnitch router prompt no longer explains individual tools. Each registered action’s
descriptionis the tool’s prompt content, and the router carries only the intent taxonomy, the universal reasoning rules, and each action’s derived argument list. Two consequences: an action with no description is invisible to the model, and a stale one is worse than none — sotests/feature_actions.test.jspins the “every AI-callable action has a description” invariant, andstats.focus-location(historically AI-callable but missing from the allow-list) is now listed inconfig/action-policy.jsonalongsidestats.timeline-control. -
GA4 ecommerce purchase reporting (catalog server) — the catalog server now reports a confirmed Paddle purchase to Google Analytics 4 as a
purchaseevent over the Measurement Protocol, mapping client id,transaction_id, value, currency, and the item list inbuild_ga4_purchase_payload. It is reported from the server rather than a browser because Paddle hosts the checkout, so no page of ours ever renders for the purchase — a client-side event on our own Paddle.js page would also miss Pro/Enterprise purchases. Each transaction is claimed once (claim_ga4_purchase_event) so a retried webhook cannot mint a second event; a Measurement Protocol event with nosession_idis otherwise unattributed. Configured withPS_CATALOG_GA4_MEASUREMENT_ID+PS_CATALOG_GA4_API_SECRET, a masterPS_CATALOG_GA4_ENABLEDswitch, aPS_CATALOG_GA4_DEBUGecho mode, and a request timeout. Covered bytests/test_catalog_ga4_purchases.py. -
Branded portal emails — the catalog server’s sign-in and email-verification messages now share one purple email shell (
_render_email_shell), so the two can no longer drift apart. The logo is shown from the deployed asset onpacketsnitch.comand attached inline as amultipart/relatedpart, so it renders wherever the HTML is viewed — including a webmail browser view, which cannot resolve acid:reference — while still carrying its own copy for clients that block remote images and for offline readers. A missing or corrupt asset degrades to the text wordmark instead of breaking a sign-in email, and the footer carries the support address plus atel:-linked phone number in both the HTML and the plain-text alternative. Outbound mail now comes fromno-reply@packetsnitch.com(the oldernoreply@andoxasploits.comreferences are retired). Covered bytests/test_catalog_email_template.pyandtests/test_catalog_email_pairing.py. -
Release notes in the About terminal — Settings → About now fetches the notes for the version it is running (
https://dl.packetsnitch.com/ps/<MAJOR.MINOR>/RELEASE) and renders them in the terminal readout, degrading to the version-only line when the fetch fails or the bridge is missing (a dev window whose main-process bundle predates the handler) instead of throwing. A single dependency-free converter,src/release-notes-text.js, turns the markdown into the plaintext the publisher writes; its Python twin (gen-release-notes.py) is pinned to the same fixtures, so the file the app downloads and the file the pipeline ships are byte-identical for the same markdown. Covered bytests/about_release_notes.test.jsandtests/release_notes_text.test.js. -
One website-path resolver — the website is a separate git repository that
.gitignoreexcludes: a siblingPacketSnitch-Website/during local development, or the CI clone underdocs/. That layout knowledge now lives in one place,scripts/website-path.js(resolveWebsiteDir), shared by the release publisher,forge.config.js, and the newnpm run sync:llm-docsrefresher instead of being re-derived in each. Covered bytests/website_path.test.js. -
Settings → Dev subtab with a “Free mode” simulation — developer-licensed installs get a new Settings subtab that is hidden for every other tier. Its “Free mode” toggle makes the install behave like a plain Free install — Pro features gated, licensed themes locked, the storefront showing Buy buttons — so the locked paths can be tested in place instead of on a second machine. The Developer license itself is untouched: the toggle only changes what every gate reads, and turning it off restores full access. Rules that keep it a testing aid: the toggle is refused unless the catalog reports the
developertier, a license reconcile that moves the install offdeveloperdrops the simulation, and the subtab’s visibility keys off the server-reported tier so free mode can always be turned back off. The shared gate (src/license-gate.js) owns the behaviour for both processes, the toggle persists insettings.dev.freeModeEnabled, and flipping it re-derives the license badge, storefront preview, theme dropdown (a theme that just became unavailable falls back), and the Pro-feature gates in one step. Coverage intests/dev_free_mode.test.js. -
PostgreSQL catalog backend — the catalog server (themes, licenses, Paddle checkout, admin portal) can now store its data in PostgreSQL instead of a local SQLite file, which is what a hosted or managed database deployment needs. Select it with
--db-backend postgres --db-dsn <uri>(PS_CATALOG_DB_BACKEND/PS_CATALOG_DB_DSN, ordb_backend/db_dsnin the YAML);--db-schemapicks the namespace and it is created on first boot. Every endpoint, CLI subcommand and Paddle/portal flow is unchanged, and the SQLite default is untouched. Moving an existing catalog across is one command —python3 scripts/migrate-catalog-to-postgres.py --sqlite catalog.sqlite3 --dsn <uri>copies every table read-only from the source, resets the identity sequences so the next insert cannot collide with a migrated id, and verifies the row counts per table (with--dry-run,--truncateand apublic-schema guard). -
Investigation playbook — asking PacketSnitch to investigate a capture now runs an ordered script rather than letting the model bumble around hoping to run into something interesting. The phases are: inventory the capture; check out every file artifact (
artifacts.list,extraction.list-carved-files,extraction.hash-file); send the threat-relevant extensions through threat intel (threat-intel.lookup-hash,conv.load-extraction-into-threat-intel); decompress every compressed artifact — ZIP/tar/CAB/7z/RAR, single-stream gzip/bzip2/lzma/lzo/brotli, and compressed HTTP bodies — and recurse back into the earlier phases on what comes out; load the unencrypted transmissions (HTTP, FTP, SMTP, IMAP, POP3, Telnet, IRC, LDAP, DNS, SNMP, TFTP, SMB, database wire protocols) into Conv and decode them; and collect every secret in the artifact store, then try it as a key against the encrypted entities in the capture (TLS/SSL, PGP, OpenSSH, WiFi, encrypted archives) to catch reused credentials. A phase is not finished while any of its items is unchecked. -
Same script for both investigator paths — the LLM rat flock renders the full playbook (rules, phases, and the extension / protocol / compression reference tables) into its discover, drill, verify, and maze-walk prompts, and the deterministic investigator loop records the playbook on the run, blubs it, and measures completion against it. A flocked investigation previously inherited the chat reply prompt (“state what you did”), which is why it narrated instead of investigating; it now gets the playbook as its base prompt.
-
Investigations only — the playbook is scoped to the
investigate-*actions. Ordinary chat, summaries, questions, and notes keep their existing prompts, and a plain question no longer spins up parallel rats. New modulesrc/investigation-playbook.js; coverage intests/investigation_playbook.test.js(29 tests). -
GA4 build/host context — every GA4 event now carries four low-cardinality, non-identifying dimensions:
app_version,os_platform(OS family),os_arch(CPU architecture), andapp_locale(language tag). The renderer stamps them insrc/gtm.jstrack()once the async runtime probe resolves (setRuntimeContext), and the main-process Measurement Protocol relay computes the same values ingetGa4RuntimeContext()so headless and CLI batches agree with the renderer. Because the context is applied after the caller’s props are filtered, an event cannot spoof it —RUNTIME_CONTEXT_KEYSremoves those keys from caller-supplied props first. -
Per-tab activity: time and bytes — a new
tab_activityevent (local pipeline:tab.activity) is emitted whenever the active tab or subtab changes, and once more at unload, carrying the dwell time and two separate byte counters:ingestBytes(capture payload the renderer ingested while that view was active) andtoolBytes(bytes the view’s own tools processed, e.g. Conv converting a payload). They are reported separately on purpose — capture ingest happens to whichever tab is on screen, while tool output is something the tab does. Ingest accounting uses the incremental staging plan so a cumulative backend snapshot is not counted over and over. The dwell is clamped to 12h per window so a sleep/resume cannot ship an absurd value. Implemented insrc/ui/main-frontend.js(flushTabActivity,setActiveTabForActivityTracking,recordTabIngestBytes,recordTabToolBytes) with emitterstrackTabActivityinsrc/gtm.jsandsrc/metrics.js. -
Analytics settings subtab removed — GA4 is driven entirely by the master Send anonymous usage metrics switch, so a dedicated Analytics subtab was redundant. Its read-only GA4 Measurement ID and API Secret fields now live at the bottom of the Privacy panel, and
SETTINGS_SUBTAB_ANALYTICS, thesettings-gtm-ga-panelelement, and thesettings-analytics-consent-statusline are gone. -
Metrics endpoint allow-list extended —
src/metrics/ps-metrics.py(_SAFE_PROP_KEYS) now acceptsingestBytes,toolBytes,os_platform,os_arch, andapp_localeso the new dimensions survive to the NDJSON sink. Covered bytests/test_metrics_server.py,tests/gtm_analytics.test.js,tests/ga4_relay_routing.test.js, and the newtests/tab_activity_tracking.test.js.
🐛 Fixes
-
A Free install can no longer be dropped onto — or pop a dialog from — a tab it is locked out of.
defaultTabdefaults tostats, whose toolbar entry point was licensed, so a brand-new Free install opened the app on the “Professional Feature Locked” dialog before the user had done anything. The Stats workspace is free and only its geolocation Map subtab (plus the timeline on it) is licensed, so the gate moved off the main tab and onto the subtab itself, and a locked tab is filtered out of both the landing-tab resolution and the session-restore dispatch (resolveDefaultLandingTab,isLicenseLockedLandingTab) instead of being opened. Internal re-renders no longer gate at all — only the user’s own click can show the dialog — and a saved session that left the Map or Conv → Threat Intel subtab open restores onto Statistics / Conversions instead of re-opening it. Covered bytests/tab_license_gating.test.js. -
Crypt → Hashes “Reverse Hash” did nothing. The button (and the “Identify Hash Types” beside it) had no click listener — the ids were in
src/index.htmlbut nowhere in the renderer — and the shared handler hard-coded the Conv → Hashes element ids, so it would have written results into the hidden Conv pane anyway. Both buttons are wired, the handler resolves the mirrored id pair, and the hashes.com reverse / identifier lookups now run the licensed-feature pre-flight so a Free install gets the “Professional Feature Locked” dialog (with a Go to Store button) instead of an inert click. -
The locked-feature dialog is actionable. Every Pro gate now shows the same generic copy — “This feature requires a Professional or Enterprise licensed install. Head over to the store to purchase one.” (shared as
PRO_FEATURE_MESSAGEbysrc/license-gate.js, the renderer and the main-process IPC errors) — with a Cancel button and a Go to Store button that opens Settings → Storefront. -
Storefront license-tier previews never loaded in a packaged build —
themes-fetch-local-previewreads the tier images fromprocess.resourcesPath, but theextraResourceentry shipped the wholesrc/assets/directory, which electron-packager places atresources/assets/(basename only), so the lookup ENOENT-ed onresources/ps-community.pngevery time the storefront opened. The three images (ps-community.png,ps-pro.png,ps-enterprise.png) are now listed individually so they land at the resources root the handler expects. -
Admin portal Logs tab keeps your place while tailing — the Logs tab auto-refreshes on a timer (default 5s), and every tick used to end with an unconditional
scrollTop = scrollHeighton all four panes, so scrolling up to read a line was futile: the next refresh yanked the view back to the bottom mid-read. Each pane now follows the tail only while it is parked at the bottom. A pane you have scrolled up in has its exact scroll offset captured before the new text is written and restored afterwards, so an auto-refresh tick cannot move it; scroll back to the end (or retype the filter / change the line limit) to resume tailing. Covered bytests/catalog_admin_logs_scroll.test.js. -
npm run makeaborted withENOENT: lstat 'docs/PacketSnitch-Website/assets/llm'— the LLM documentation bundles listed underpackagerConfig.extraResourcestill pointed at the pre-relocation website path, and electron-forgelstats every entry while finalizing the package, so packaging failed outright before the installers were built. The curated bundles (packetsnitch-help.txt,packetsnitch-dev.txt) are now committed atsrc/data/llm/so the packager always has a source, with the website checkout preferred when present;scripts/website-path.jsresolves either layout (repo-rootPacketSnitch-Website/or the CI clone underdocs/) for the packager, the release publisher, and the newnpm run sync:llm-docsrefresher. The offline docs fallback insrc/main.jsnow resolves the packagedresources/llm/directory instead of an unreachable.webpack/docs/...path, which also had it silently dead in every build. -
The Linux backend build no longer drags in the system icon theme — the backend is a headless analyzer that never plots, but matplotlib still reaches the dependency graph (scipy pulls it in for its
.plot()helpers and scapy imports it lazily), and its GTK backend makes PyInstaller’shook-gi.repository.Gtkcollect the entire system icon and theme trees — on a typical Linux desktop roughly 230k files / 1.5 GB of/usr/share/icons. That payload, not any single entry, is what broke the onefile build: the package ballooned past 800 MB and every launch extracted ~2.2 GB / ~440k inodes into a fresh/tmp/_MEIxxxxxx, exhausting a tmpfs/tmpso that a latesymlink()failed withENOSPCand the bootloader aborted. The build now passes--hook-config gi:icons=[] gi:themes=[]to opt out of the bulk collection; the GTK typelibs themselves are still collected, so this is a pure payload reduction with no runtime behaviour change. -
Legacy catalogs could not be upgraded to a PostgreSQL-ready schema —
_migrate_installs_tier_checkrebuilds theinstallstable (SQLite cannotALTERaCHECKconstraint) and hard-coded the current seven-column shape, which broke every catalog built before the demo/portal columns existed in two ways: the positionalINSERT INTO installs SELECT * FROM installs__old_tier_checkraisedtable installs has 7 columns but 12 values were supplied, and the rebuilt table’slicense_tierstill defaulted to'free'whileensure_installinsertsdemoexplicitly, so the next lazy registration raisedCHECK constraint failed: license_tier IN (...). The rebuild now derives its column set from the live table and copies by name, so a pre-trial (7-column) and a demo-era (12-column) catalog both open cleanly, keep every value, and acceptdemo/developerafterwards. Covered bytests/test_catalog_schema_migration.py(three new cases, each failing against the old implementation). -
A paid tier was reported as owned but 403’d on download —
/catalogand/licensesadvertise every catalog item asowned: trueforprofessional/enterprise/developer, and the renderer’sbackfillMissingOwnedThemeswalks exactly those entries, but/themes/<id>/downloadgated on the raw per-themelicensesrow. An install that had just been upgraded — or a free seat inheriting Enterprise through the customer fan-out — therefore saw a Download button that failed. The endpoint now gates ontheme_is_effectively_owned, the helper the catalog was documented around; the legacy 403 for a free install with no license is unchanged. Covered by four HTTP-level cases intests/test_catalog_admin_api.py. -
Stale test expectations and a dead suite in the backend run — five catalog tests still asserted the pre-trial contract (
unknown install → free) and the pre-narrowing fan-out (aprofessionalsibling promoting afreeseat);demois the first-sight tier now,freeis the post-trial tier the poller writes, andprofessionalis seat-scoped by design, so the assertions were corrected to the documented behaviour.tests/test_catalog_listable_gate.pyloadedps-catalog.pyfrom the retiredsrc/PacketSnitch-Pro/Servers/Catalog/path and raisedFileNotFoundErrorat import, aborting the whole pytest run before any test executed;tests/test_backend_legacy_output_dir.pyexercised_resolveLegacyOutputDirectoryand friends, none of which has ever existed insrc/backend/snitch.py, and was removed.
v3.3.0 - 2026-10-12
Type: minor
Google Analytics 4 product analytics with a locked destination, a storefront license-tier preview and SALE badge, catalog operator tooling (item upsert, seat listing, seat reassignment, payment-gated license reconciliation), and a smarter Ask PacketSnitch that learns flock techniques, resolves a host by its role on the heatmap, and unpacks whole archives in one click.
✨ Features
- Google Analytics 4 product analytics (opt-in, locked destination) —
the desktop app now reports anonymous, allow-listed UI events to the
maintainers’ GTM/GA4 property when the single Privacy → Analytics consent
switch (
privacy.metricsEnabled) is on.src/gtm.jswraps GTM and gtag with a sanitized event API (track,trackPageView,trackTabSwitch,trackFeatureUse,trackTiming,trackError,trackException): every event name is validated against^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)?$, every property is filtered through an allow-list (GTM_SAFE_PROP_KEYS), numeric fields are clamped, and error text is scrubbed before it leaves the process. Because the renderer is served from afile:///app://origin where the_gacookie does not persist, gtag’s identity is pinned to the install UUID instead of minting a throwaway user on every launch. The synthetic page path is rooted at a distinct/PS-App/prefix so the client’s invented paths (/PS-App/stats,/PS-App/data-tools/conversions) are separable in one GA4 regex from the marketing site’s real paths, and the same dimension is namespacedps_app.PS-App.*for the local metrics pipeline. There is deliberately no per-feature toggle: GTM/GA load only under the master consent switch, and the GTM container id, GA4 measurement id, and write-only GA4 API secret are compiled-in locked constants thatnormalizeSettingsre-asserts on every read — a hand-editedsettings.jsoncannot redirect telemetry and the Analytics panel shows all three read-only. The main-process relay (sendMetricsToGa4, GA4 Measurement Protocol) forwards the same scrubbed events; the API secret is never logged and never echoed to the renderer. The metrics endpoint (src/metrics/ps-metrics.py) mirrors the same allow-list (including the newpage_path,page_title,page_location, andfeature_namekeys). Covered bytests/gtm_analytics.test.jsandtests/csp_gtm_allowlist.test.js. - Storefront license-tier preview + SALE badge — the Storefront panel’s
license section now renders a tier preview by default, mapping the
active tier to a bundled image (demo/free → community, professional → pro,
enterprise → enterprise) served as a data URI through a new
themes-fetch-local-previewIPC handler; the images ship as anextraResource.get-install-uuidnow routes throughensureLicenseInstallUuid()so the UUID is generated on first look instead of returning empty. Catalog items (themes and licenses) can be flagged on sale — a newsalecolumn in the catalog API drives a red SALE badge on the storefront and is toggleable from the catalog admin portal and via the newps-catalog.py add-item --sale/upsert-item --saleflags. - Catalog operator tooling: item upsert, seat listing, seat reassignment —
ps-catalog.pygains anupsert-itemsubcommand that updates an existing catalog item in place (preserving fields andtheme_jsonunless explicitly overridden, so an operator can flip metadata such as--salewithout re-supplying the artifact) or inserts a new one;list-seatsprints every install UUID paired with a Paddle customer (by--customer ctm_…or--email, with--json) as an operator debugging view of enterprise multi-seat accounts; and the self-service portal gainsPOST /portal/reassign(move a seat-scoped license from one install UUID to another, gated on a valid portal session whose UUID is one of the two endpoints and refused for Enterprise, which is customer-wide) plusPOST /portal/logout(courtesy stateless sign-out). The customer-group fan-out now promotes every UUID under a customer to the group’s highest tier — a single Enterprise license makes free seats readenterprise— with the deliberate exception of an install that has nopaddle_customer_id. The catalog serve command also prints the help page instead of a traceback when its bind address is already in use. - Payment-gated license reconciliation — license tier upgrades are now
granted only when a completed payment exists: a
paddle_customer_idalone (minted when a buyer enters a ZIP code for tax estimation) no longer grants a paid tier — apaddle_subscription_idmust be present, on both the webhook path and the reconciliation path. A periodic_reconcile_all_installssweep pulls Paddle transactions, maps active subscriptions to their customer tiers, and corrects drifted rows (unpaid licences revert to free, demo installs convert to free when their trial expires in the poller), with detailed reconciliation logging. - Host-role resolution for map focus ("open the attacking host on the
heatmap") — the Stats heatmap locator (
stats.focus-location) used to require a literal target (IP, hostname, city/country, point id, or coordinates), so a request that named a host by its role failed with “No heatmap point matches the requested host/location”. The chat path now builds an investigation digest from the cached rat-flock result — the findings, resolved goals, and final report — crossed with the mapped heatmap points: it inventories the IPs/hostnames the investigation mentioned, resolves the rolesattacker,victim,exfiltration-destination,server,client, anddnsby taking the host named nearest the role keyword in the same clause (so “the attacker A exploited the victim B” does not resolve both roles to A), and scores the mapped points that carry those hosts. The digest is embedded in both the router prompt and the adaptive next-step prompt, so multi-step requests keep the role→host mapping across steps; when no investigation is cached the prompt tells the model to clarify rather than invent a host.stats.focus-locationaccepts a new{ role }argument (and infers one from a free-text field), and the immediate heatmap shortcut fires the role path directly once the role actually resolves. When a role cannot be resolved, the error now names the closest mapped locations and the role vocabulary instead of a bare dead end. Covered bytests/ask_investigation_digest.test.js. - Inexact location names no longer fail the heatmap locator —
matchStatsLocationHeatmapPointis now two-pass: an exact pass (IPs, hostnames, and point ids stay strictly exact) and, only when nothing matches, a fuzzy pass over city/country/label that ignores case, punctuation, and token order (“new york” → “New York City”; “united states of america” → “United States”). Partial or wrong identifiers never match fuzzily, and empty fields never act as wildcards. - Flock goal learning (shared techniques + resolved answers) — a rat that
reaches its goal now writes down how it got there. Each rat can declare a
solved goal in its output with a
SOLVED: / TECHNIQUE: / ANSWER:block; the technique (the replayable steps) is recorded on the scratchpad and, once resolved, mirrored into a durable cross-run store at<userData>/flock-goals.jsonkeyed by a normalized goal tag. Every prompt handed to a rat (maze-walk, discover, drill, verify, synthesize) carries a “Goals already reached by the flock” block listing the proven techniques — ranked by confidence, tagged with their provenance (this flock vs. a prior flock) — so a later rat copies the path rather than re-deriving it. Reached goals also raise the scratchpad usefulness score, so convergence is detected sooner and the flock spends fewer turns. Techniques that keep failing are retired after repeated failures (markGoalTechniqueStale) so a stale path stops steering rats. A cancelled or crashed flock still harvests the goals it did resolve. The chat surfaces each reached goal (🎯) and the number of techniques inherited from earlier investigations, and the fallback synthesis/report builders list the reached goals with their answers and techniques. Covered bytests/flock_goal_learning.test.js. - Extract All for archives (UI + AI) — the Conv Extraction subtab now
has an Extract All button next to Decompress and Browse Archive: it
unpacks every safe file member of the detected ZIP/tar/CAB/7z/RAR archive in
one run and registers each in the Artifact Store (bounded to 50 members;
directory entries and members without a sanitized safe path are skipped; a
running “N of M” progress line replaces the per-entry clicks). The same
primitive is exposed to the AI as the
extraction.extract-allregistry action — reachable through the capability catalog and the planner prompt — so an investigation can unpack a whole archive without loopingextraction.extract-entryby hand. Extraction stays zip-slip safe: every member is written only to its sanitized safe path and the backend rejects any entry whose path escapes the archive root. Covered by new cases intests/feature_actions.test.jsandtests/extraction_panel.test.js.
🐛 Fixes
- Artifact-store cache refreshes on extraction and on a timer — the
keystore panel’s file-artifact cache now reloads whenever an extraction
registers a new artifact, and a cancellable interval timer
(
createIntervalTimer, rebuilt once a minute) keeps the cached manifest in step, so an artifact the AI just carved is immediately resolvable by name without a manual rescan. The timer is torn down on session reload/clear alongside the other keystore caches. - GA4 timing/feature/error dimensions were being dropped —
feature_name, thetiming_completefields (timing_category,timing_variable,timing_value,timing_label), and the exception fields were missing from the allow-list, so those events shipped with no measurements; they are now permitted, clamped, and namespaced (see the analytics feature above).
v3.2.2 - 2026-10-08
Type: patch
Ask PacketSnitch “assume yes to all” research templates that suppress every approval prompt, storefront account-tier entitlement (plus the install UUID surfaced right in the Storefront panel), a theme-overridable CSS variable surface, and ps-metrics / catalog-admin logging improvements.
✨ Features
- “Assume yes to all” research templates — two new Ask PacketSnitch
research templates join the approval-policy ladder:
assume-yes-budgeted(“Assume yes to all (budgets enforced)”) andassume-yes-unbudgeted(“Assume yes to all (budgets ignored)”). Both suppress every approval prompt — cost-bearing (quota / paid) calls, actions flaggedrequiresConfirmation, and the caller-policy allow-list gate — so the agent runs unattended. The budgeted variant still enforces the step, runtime, paid-spend, quota, and repeated-lookup budgets; the unbudgeted variant suspends them entirely, so the only stop is the model finishing (or the user cancelling). The policy carries a newenforceBudgetsflag (isAssumeYesTemplate/templateEnforcesBudgetsinsrc/ui/main-frontend/action-registry.js), the planner is told it may return “as many steps as the request needs” when budgets are off, and the investigator loop accepts anunbudgetedbudget that keeps its counters for the run report but never halts (Number.POSITIVE_INFINITYcaps, unlimited candidate set, “unlimited” quota label).buildInvestigationBudgetderivesunbudgetedfrom the active template and strips it from any LLM-authored override, so the model cannot exempt itself. Settings → LLM disables and annotates the budget fields (“Budgets are ignored”) when the unbudgeted template is selected, disables the unknown-cost prompt for both assume-yes templates, and the settings normalization accepts the new values while rejecting unknown ones. Covered bytests/keyboard_shortcuts.test.jsandtests/investigation_budget_override.test.js. - Storefront account-tier license entitlement — the storefront now folds
the account’s paid tier into each catalog entry’s
ownedflag instead of keying purely off per-item purchases. A paid tier (professional<enterprise) owns every theme outright, and license SKUs compare by rank, so a Professional install owns the Professional SKU and still sees Enterprise as a purchasable upgrade, while an Enterprise install owns both and is never offered a downgrade.resolveCatalogEntryOwned/licenseTierRankinsrc/main.jsmirrorLICENSE_TIER_RANKon the catalog server; the renderer keys its Owned badge and hides the Buy button off the resulting flag. Paddle webhook grants are upgrade-only for the same reason, and the customer-group seat fan-out reports the highest non-free tier. Covered bytests/license_entitlement_owned.test.jsandtests/test_catalog_tiers.py. - Install UUID in the Storefront panel — a read-only “This Install”
section on the Storefront subtab shows the install UUID that licenses and
theme purchases are bound to, with a one-click Copy button. The value is
read from the same
installapi.getInstallUuidbridge the license path uses and is re-read every time the panel opens, so a first-run UUID minted after startup still appears. Covered bytests/storefront_install_uuid.test.js. - Catalog self-service portal: names + verified email — the portal now
separates signed in (holding the install UUID) from email proven: name
and email are collected at the new
POST /portal/profileendpoint, but the address is only written toinstalls.customer_emailonce it is verified — either by a 6-digitPOST /portal/verify-emailcode or by a completed Paddle purchase (which proves the address during checkout).GET /portalreturnsname,emailVerified,profileComplete, and agreeting(falling back to the email local-part, then “Welcome back”). Code TTL, max attempts, and resend spacing are configurable viaportal_verification_code_ttl_seconds/portal_verification_max_attempts/portal_verification_resend_seconds. Covered bytests/test_catalog_portal_profile.pyandtests/test_catalog_email_pairing.py. - ps-metrics undated log aliases + catalog admin Logs tab — each ps-metrics
NDJSON stream gains an undated hardlink alias (
events-current.ndjson,installs-current.ndjson,errors-current.ndjson) so an operator cantail -fa stable path without first working out today’s date. The aliases are hardlinks (no extra disk or I/O), are atomically re-pointed on rollover (os.link+os.replace), and are opened at startup — so they exist, possibly empty, as soon as the server is up. The catalog server’s browser admin portal renders the ps-metrics server log and the raw NDJSON event stream on its Logs tab (configured withmetrics_log_path/metrics_events_dir, capped at the trailing 2000 lines per pane). Covered bytests/test_metrics_server.py. - Theme CSS variable surface + theme polish — the theme engine exposes new
overridable variables (
--dropdown-menu-bg,--stats-tag-button-text/--stats-tag-button-text-hover,--loading-screen-bg,--analysis-panel-bg/-text,--analysis-header-text,--analysis-content-bg/-text,--tab-btn-border) that default to the previously hardcoded values, so existing themes render unchanged while theme authors can now restyle the custom dropdowns, stats tag labels, capture loading screen, analysis workspace, and the main top-tab outline. Brushed Steel and Light fix unreadable input / stats-tag text (#9aa3af→#000000), Corporate Blue gains a full analysis/dropdown/loading palette, Nilla Horizon re-tints its tab outline and heatmap, and Haruhi Suzumiya / Weebo / Pink Lace set a light dropdown background. Covered bytests/themes_subtab.test.js.
🐛 Fixes
- Error messages fade out — the full-screen error overlay now fades out after ~8 seconds (0.5s CSS opacity transition) and is still written to the activity log for later review; clicking the overlay dismisses it immediately and cancels the pending fade.
- Ask PacketSnitch context prompt hardening — the “Current PacketSnitch
context” JSON no longer includes the filter-key catalog (it is surfaced
separately as an explicit “Valid filter keys” list) so the model stops
confusing key names with packet data values, and the appended
action-response prompt now carries the previous plan step results. Plan step
results also persist across chat turns (
askPacketSnitchRecentStepResults, capped at 10) so a follow-up such as “extract everything from that archive” resolves an artifact a previous turn already listed; context changes, a new pcap load, and Clear chat all wipe the record. - Wildcard name patterns —
compileNamePatternnow escapes regex metacharacters before converting the wildcards, so a literal.before a*(for examplemetasploit.*) is no longer swallowed by the wildcard expansion. - ps-metrics first batch — a freshly restarted, idle metrics endpoint now
creates today’s streams (and their
*-current.ndjsonaliases) at construction instead of waiting for the first batch, so the stable log paths exist exactly when an operator goes looking for them.
v3.2.1 - 2026-10-05
Type: patch
Modular feature-action contribution registry (one AI-callable action surface per feature area), artifact-store caching so the LLM resolves carved files by wildcard / substring fallback, and richer Stats-map location focus with hostname support.
✨ Features
- Modular feature-action contribution registry — the AI-callable action
catalog is now contributed by per-feature modules in
src/ui/main-frontend/feature-actions/(bookmarks,notes,stats,extraction,evidence,session-filters,capture,decoders,locations,artifacts). Each module exportscreateActions(deps)→ an array of descriptors; the registrar normalizes them (defaultsallowedCallersto["ai"], fillsdomain/label, drops descriptors without aninvoke) and registers them all into the live action registry. Because the rat-flock tool catalog (buildFlockToolCatalog) is derived fromactionRegistry.list(), every newly registered action is automatically reachable by the parallel investigator with no other wiring required. Covered bytests/feature_actions.test.js,tests/action_registry_versioning.test.js, andtests/feature_actions_locations_artifacts.test.js. - Rat-flocking parallel investigation — Ask PacketSnitch can now run a
parallel “rat flock” investigation: a shared scratchpad of findings is
built up by multiple primitive LLM calls (“rats”) running in phases
(
discover→drill→verify→synthesize→report), each spawning a configurable number of parallel investigators (flockRatCount) that see the evolving state and can drill into discoveries, cross-reference findings, or resolve contradictions. The orchestrator lives insrc/flock-orchestrator.js, with the dynamic tool catalog insrc/flock-tools.js(derived from the live action registry), delta scratchpad persistence as append-only JSONL in<userData>/flock-scratchpads/<investigationId>.jsonl(crash-recoverable viarebuildScratchpadFromDeltas), and an in-flight controller registry in the main process sollm:flock-cancelcan abort a running flock. A rat budget of zero collapses the pipeline to a single sequential LLM call. Covered bytests/flock_maze_backtrack.test.js,tests/flock_solo_weave.test.js, andtests/flock_tool_catalog.test.js. - Stats locations + anomalies exposed to the AI — new read-only actions
stats.locations.list/stats.locations.getsurface the geolocated heatmap points (city, country, lat/lon, packet weight, source IPs, and hostnames) plus the capture-level “Most Active Mapped Locations” list without opening the Map tab, andstats.anomalies.listflattens the grouped Stats anomalies (protocol anomalies, port scans, brute force, baseline outliers, embedded content) into a bounded list with stable ids. A newstats.open-subtabaction opens the statistics / map / anomalies subtabs programmatically. Covered bytests/stats_location_focus.test.js. - Stats-map focus improved —
stats.focus-locationnow accepts a hostname alongside IP / city+country / point id / raw lat+lon, validates coordinates before resolving (-90..90/-180..180), and matches against the heatmap by id, IP, hostname, city/country, or location label. The return value carries the resolvedhostnamesso the caller can chain afilter-queryon the discovered names. NewcollectStatsLocationHostnames/getStatsLocationHostnameshelpers insrc/ui/panels/stats-panel.jsflatten the backend’s reverse-DNS shape (nested arrays,dns.hostnames,dns.qname(s)) into a single hostname set. - Extraction actions resolve via wildcard / substring fallback — when an
exact
fileId/fileNamematch fails,extraction.list-archive,extraction.decompress, andextraction.extract-entryretry against the artifact store snapshot using glob wildcards (*,?) and case-insensitive substring containment, so an LLM that generates a slightly different name (case, partial name, glob) still resolves the correct carved file.extraction.extract-entrynow also extracts every member of an archive whenentryPathis omitted, registering each into the artifact store + Stats. Covered bytests/feature_actions.test.js. - Artifact-store caching for the LLM — the artifact store snapshot is now
cached and surfaced through
getFileArtifactSnapshot, so extraction actions can read persisted carved-file metadata (including base64 bytes) without re-resolving the live registry on every call. Each extracted member carries an explicitfileId/fileNameso the artifact store and the Stats extraction registry stay in sync. - Adaptive step-by-step plan reflection — Ask PacketSnitch plans no
longer execute their pre-planned list blindly. After every completed step
the executor hands the step’s actual results back to the LLM, which
analyzes them and decides the next step (stop when the goal is reached or
the path dead-ends, or replace the remaining steps with ones derived from
what the last step returned). Delegated steps are normalized through the
same plan validator as classifier output; any malformed reflection falls
back to the pre-planned remaining steps. A reflection budget (4 per run)
and the research step budget cap the extra LLM calls, and the new
settings.llm.planStepReflectEnabledtoggle (defaulttrue) disables the reflection entirely. Step results now persist across chat turns (askPacketSnitchRecentStepResults, capped at 10) so follow-ups like “extract everything from that archive” resolve the archive a previous step already listed; context changes and Clear chat wipe the record. Covered bytests/ask_packet_snitch_adaptive_plan.test.js.
🐛 Fixes
- Ask PacketSnitch subtab routing — context plumbing between the renderer,
the Ask PacketSnitch store, and the LLM dispatch now survives mid-stream
navigation hops without dropping the active plan or rerunning the working
message; subtab routing through
ask-psno longer drops the active conversation. Covered bytests/ask_packetsnitch.test.js. - Single threat-divergence logic — the LLM handler now applies a single consistent threat-divergence pass instead of re-evaluating each indicator independently, so contradictory findings collapse into one coherent verdict rather than surfacing as competing claims.
- LAN location auto-grab for metadata — the backend now attempts to
auto-grab the location of each LAN interface in the session for metadata,
so GeoIP / heatmap enrichment has a usable origin even when the analyst
never manually tagged a subnet. Covered by
tests/capture_location_drop.test.js. - Investigations stop cleanly on session reload / pcap load — rat-flock investigations now tear down properly when a session reloads or a new pcap loads, instead of leaving orphaned in-flight LLM calls that could fire against the wrong capture.
- Image decoder regression — a fallback decoder path restores image display (plus EXIF data) when the primary decoder fails, so corrupted or unusual images no longer blank the detail view.
- UI chrome alignment — the minimize button next to the quit button and the dash in the window title bar now align correctly across window states.
v3.2.0 - 2026-09-25
Type: minor
Backend threat-intel response caching (VirusTotal + Shodan InternetDB), LLM context tracking + floating panel ergonomics, and native RAR archive support in the Conv extractor.
✨ Features
- Backend threat-intel cache (VirusTotal + Shodan) — the Python
backend now caches both
virustotalandshodanlookup responses on disk (<userData>/cache/threat-intel/threat-intel-cache.json, 24-hour TTL). Repeat lookups against the same key hit the cache instead of the upstream rate-limit budget, the cache survives backend restarts, and private/loopback IPs short-circuit to a cleansupported: false, skippedReason: "private-network"payload so RFC1918 addresses never reach the public reputation API.loadThreatIntelCacheFromDisk()rehydrates the in-memory map on startup;readThreatIntelCache/writeThreatIntelCacheare the only sanctioned read/write seams;isNonPublicIpv4OrIpv6AddressandbuildNonPublicIpResponsekeep the VT and Shodan payload shapes consistent with a live response so the renderer renders either branch identically. Network errors and unauthorized responses are deliberately not cached. Covered bytests/test_backend_threat_intel_cache.py. - RAR archive support in Conv’s Extract subtab —
snitch_extract.pyand the Node-sideinferExtractionFormatFromBytes/list-archive/extract-archive-entryIPC path now handle.rararchives alongside.cab,.7z,.zip,.tar,.gz,.bzip2, and.zstd. Both signatures are detected — RAR 5.0 (Rar!\x1a\x07\x01\x00) and RAR 1.5–4.x (Rar!\x1a\x07\x00) — with RAR5 checked first so the longer signature never falls through to the seven-byte RAR3 match. Listing always works (rarfile.RarFile.infolist()); extraction of non-solid RAR3 archives uses rarfile’s pure-Pythonrar3_decompress, and solid RAR3 + RAR5 surface a clean “installunrar/unar” diagnostic instead of a stack trace. Bundledsamples/rar/sample.rarfixture plus a SHA-256-pinnedextracttest and a system-unrarbyte-equality oracle test ship intests/test_backend_rar_extract.py. - Better LLM context tracking — Ask PacketSnitch now keeps the active conversation context (filter query, target host, current packet, current subtab, recent lookup results) in sync with the LLM at every navigation hop. The renderer threads the same context shape into both Ask PacketSnitch and the AI research orchestrator so a question fired from one subtab stays anchored when the analyst navigates away mid-stream, and the working message reflects the live context instead of a stale snapshot.
- Cohere as a first-class LLM provider — Cohere joins Ollama
and OpenRouter in Settings → LLM → Provider. The
generateCoherehandler insrc/llm.jsposts to the Cohere v2 chat completions API with the same per-call{ maxTokens, temperature, think }overrides the other providers accept, and the response is normalized to the shared{ response, model, done }shape. The default model iscommand-a-plus-05-2026. - Resizable / movable floating panels — the log, chat, and
other auxiliary panels now expose drag handles and a resize grip
so analysts can pin them where they want them on screen. State
survives across panel close/reopen within a session, and the new
src/ui/panels/draggable-panel.jsmodule keeps the resize math off the main render path. Covered bytests/draggable_panel.test.js.
🐛 Fixes
- Ask PacketSnitch working message — the in-flight
“working…” message is now correctly invalidated when the active
packet or filter changes mid-call, so analysts no longer see a
stale summary after navigating away from the original target
before the LLM responds. Covered by
tests/ask_packet_snitch_working_message.test.js. - Ask PacketSnitch context-change regression — context
plumbing between the renderer, the Ask PacketSnitch store, and
the LLM dispatch now survives mid-stream navigation hops without
dropping the active plan or rerunning the working message. Covered
by
tests/ask_packet_snitch_context_change.test.js.
v3.1.2 - 2026-09-21
Type: patch
Catalog purchase flow hardening, theme gating for developer builds, and loading-animation colorscheme alignment.
✨ Features
- Developer build theme gating — internal theme gating logic was updated so developer builds can opt into the full theme catalog without surfacing purchase-only themes to end users.
🐛 Fixes
- Catalog purchase flow — hardened the catalog purchase callbacks across several iterations to repair Paddle purchase detection, list installation mismatches, redirect handling, 404 prevention on stale catalog responses, billing cycle updates, and database corruption recovery. Purchases now reliably change the license tier and the catalog reflects the user’s entitlement state.
- Catalog preview URL support — the catalog now accepts preview URLs so unfinished themes can be inspected before promotion.
- Catalog response dedup — duplicate catalog responses are now collapsed so a single fetch surfaces a single coherent catalog.
- Loading animation colorscheme — the splash/loading animation screen now follows the active colorscheme instead of a hard-coded palette.
v3.1.1 - 2026-09-19
Type: patch
License-tier update fix.
🐛 Fixes
- License tier update — fixed a regression where purchasing a license did not propagate the new tier to the running app until a restart. The tier now updates in place.
v3.1.0 - 2026-09-18
Type: minor
Catalog server and admin portal move into the primary project, AI investigator evidence substrate and budgeted autonomous loop, and a rebuilt Stats → Map timeline mode.
✨ Features
- Catalog server and admin portal in-tree — the
ps-catalog.pycatalog server (themes, licenses, Paddle checkout, device-binding) and the browser-based web admin portal (src/web-admin/) now live in the primary PacketSnitch project rather than a sibling repo.catalog/was renamed tosrc/catalog/. - AI investigator evidence substrate (Phase B) — a first-class
EvidenceRefschema ({evidenceId, kind, label, sensitivity, target, provenance}) ships viasrc/ui/evidence-ref.js. Notes carryevidenceReferences: EvidenceRef[]and render the array as a clickable chip rail; clicking a chip callsrestoreEvidenceRefwhich re-opens the right tab/subtab, applies the filter, jumps to the packet, and emits a transient status-bar toast. Refs are deduped by deterministic FNV-1a id and sensitivity-gated against the user’s privacy setting. - AI autonomous investigator orchestrator (Phase C/D driver) —
src/ui/main-frontend/investigator-loop.jsnow drives a budgeted, interruptible investigation loop: it enumerates candidates, filters them by the active scope, walks Phase 1/2 actions, surfaces incremental progress viaappendAnalysisBlub, and emits provenance-linked notes. Plan dead-ends now route through one decision instead of infinite-retry looping. - AI action allowlist expansion —
investigate-pcap,investigate-selection,investigate-anomaly, and theevidence.*/investigate-*action ids are registered in the AI action allowlist. - Map timeline mode — the Stats → Map timeline mode is now
functional: a lead-in animation, a resizable dividing bar between
the map and the timeline strip, a clock-driven scrubbing handle,
and a
stats-map-resize-observerthat keeps the projection in sync with the visible viewport. Missing-items / empty-network states now render dedicated panels instead of leaving a blank area. - Stats splitter slider — the Stats panel now exposes a draggable splitter (with persisted ratio in settings) so analysts can size the map and timeline strip to taste.
🐛 Fixes
- Stats → Map projection autocalibration — the auto-calibrated world-map projection now handles edge cases that previously left hosts off-screen, and the calibration result is locked once per session so jitter doesn’t move pinned hosts.
- Stats → Anomalies integration — the four structured detectors (port-scan, brute-force, baseline outliers, embedded high-entropy cleartext) now share one engine with the Session Threat Score so the two views never disagree.
- License schema and demo gate enforcement — the catalog-side
license schema was rebuilt to support the demo gate and the
Pro/Enterprise tier list properly; the desktop entitlement rules
in
src/license-gate.jswere updated to enforce the new tier boundaries consistently across the renderer and main process.
🧪 Tests
- Added
evidence_ref.test.js(41 tests),notes_evidence.test.js(14 tests),investigation_scoring.test.js,investigation_budget_helper.test.js,investigator_loop.test.js(16 tests),safe_invoke_registry_action.test.js,ask_packet_snitch_chat_reset.test.js,ask_packet_snitch_chat_restore.test.js, andask_packet_snitch_chat_session.test.js. - Added
stats_timeline_clock.test.js,stats_timeline_seen_hosts.test.js,stats_topology.test.js,stats_networks_empty.test.js,stats_networks_ui.test.js,stats_splitter.test.js,stats_projection_autocalibrate.test.js,stats_subtab_resolver.test.js,missing_items_dialog.test.js,stats_networks_required.test.js, andcompare_packets_selection.test.js. - Added
analysis_blub_dedup.test.js,capture_location_drop.test.js, and updatedlogging.test.jsandcompare_decoder_layout.test.js.
v3.0.1 - 2026-09-15
Type: patch
CI runner cleanup, map slider polish, AI chat tuning, and a few small UX improvements.
✨ Features
- GitLab CI runner refresh — CI YAML updated for
sudoaptpaths and a working test pipeline. - Upload only
.exeto the website — release artifacts no longer upload debug intermediates to the website release directory. - AI UX controller improvements — the AI chat surface got a round of polish (response rendering, context-aware suggestions, conversation history handling).
- Better AI chat — the in-app chat gained a clearer typing state, a copy-last-reply affordance, and a smoother message stream.
🐛 Fixes
- Keybind defaults — restored sane default keybindings for the common capture navigation shortcuts and adjusted a few user-facing defaults.
- CI progress meter — the GitLab pipeline now reports a real progress bar instead of a static indicator.
🧪 Tests
- Minor follow-on coverage for the AI chat tweaks (covered by the
broader
ask_packetsnitch.test.jsandask_packet_snitch_*suites added in 3.1.0).
v3.0.0 - 2026-09-12
Type: major
PacketSnitch 3.0 adds analyst-directed packet comparison and turns Ask PacketSnitch into a safer, documentation-grounded assistant for capture investigation and application help.
✨ Features
- Compare Packets workspace — the Compare tab now includes a packet-level comparison view alongside capture-source comparison. The current Host Data packet can be compared with a packet from the active filtered result set, with optional same-protocol filtering and searchable candidates.
- Byte and field comparisons — compare payload bytes or full packet bytes, view MD5/SHA-256 metrics and SSDEEP-style similarity, inspect changed byte ranges, and compare decoded protocol fields. Header metadata can also be included in the comparison.
- Ask PacketSnitch action routing — Ask PacketSnitch can query and filter the current capture, open supported workspaces, send data to Conv or Crypt, add results to Notes, and answer application-help questions through a bounded documentation lookup action.
- Grounded documentation lookup — help questions search fixed, application-controlled PacketSnitch documentation sources with bounded responses, caching, stale-cache fallback, and no user-supplied URLs or file paths. Retrieved text is treated as reference material, not executable instructions.
- Configurable routing retries — Settings → LLM now exposes Ask PacketSnitch routing retries (0–5). Empty or malformed model routing output can be reworded and retried before the action is rejected.
🐛 Fixes
- Ask PacketSnitch reliability — normalized more model response shapes, repaired common generated-filter syntax mistakes, and kept unsupported actions, tabs, destinations, URLs, and executable content outside the allowlisted action surface.
- Compare-tab activation — Compare Packets is now enabled and renders a stable capture/sources/packets subtab layout instead of a disabled placeholder.
- Release-version reporting — startup and About release checks now handle version information more consistently across the packaged and development paths.
🧪 Tests
- Added packet comparison helper, decoded-field comparison, UI layout, and subtab visibility coverage.
- Added Ask PacketSnitch routing, documentation lookup, retry, and safety validation coverage.
v2.8.2 - 2026-09-05
Type: patch
PCAP merge enhancements, six new protocol decoders (SSDP UPnP, gRPC, DHCPv6, mDNS, LLMNR, EPMAP), improved plain-text decoder, and gRPC stream reassembly fix.
✨ Features
- Six new protocol decoders — SSDP UPnP, gRPC, DHCPv6, mDNS, LLMNR,
EPMAP (Conv → Decodes + sidebar) — extends protocol coverage with
SSDP (Simple Service Discovery Protocol / UPnP), gRPC
(Google RPC over HTTP2), DHCPv6, mDNS (multicast DNS),
LLMNR (Link-Local Multicast Name Resolution), and EPMAP
(DCE/RPC Endpoint Mapper). All are registered in
SUPPORTED_DECODER_PROTOSand auto-detected viaPROTOCOL_DECODER_HINTS/PORT_DECODER_HINTS. - gRPC stream reassembly fix — fixed a bug in the gRPC decoder where stream fragments were not being correctly reassembled, causing partial or corrupted output for multi-frame gRPC calls. The decoder now properly buffers and reassembles streaming RPC responses.
- Plain-text decoder improvements — the generic plain-text decoder was enhanced with better boundary detection and charset handling for more reliable text extraction from raw streams.
- PCAP merge detail buckets — the Stats → PCAP Comparison subtab now shows per-bucket detail (packet counts, byte totals, timing distribution) rather than just aggregate stats, making it easier to identify which traffic segments differ between captures.
- Shannon entropy color-coding improvements — the entropy visualization in PCAP comparison now has finer color gradation for better differentiation of low/medium/high entropy regions, and the entropy symbol was corrected for consistency.
- Multi-source bookmarking and targeting fix — fixed bookmark creation and target resolution when a session has multiple capture sources, ensuring bookmarks correctly reference the intended source and stream.
- Better merge support — various improvements to the PCAP merge workflow including better handling of overlapping time ranges and deduplication of duplicate packets across sources.
🐛 Fixes
- gRPC stream reassembly — see Features above.
v2.8.0 - 2026-09-04
Type: minor
⚠️ Versioning scheme switch. This release moves from the prior
MAJOR.MINOR.<build-counter>scheme (e.g.2.7.1735) back to a classicMAJOR.MINOR.PATCHtriple. The build-counter third part was getting out of order because of a build-tracking bug that could bump the counter on stale or aborted builds, which then caused release candidates to publish with version numbers that didn’t reflect their actual chronological order. Starting with v2.8.0 we ship a cleanMAJOR.MINOR.PATCHnumber per release; bump the patch for fix-only releases, the minor for new features, and the major when we cross a boundary.WebSocket transport, eight new protocol decoders (MNDP, Modbus, S7comm, DNP3, CDP, HSRP, LACP, OSPF, STP), the Artifacts store overhaul, Conv UX polish, a Stats → Conv jump-to-stream shortcut, Host Data alignment fixes, and the removed deprecated LLM checkbox.
✨ Features
- WebSocket support + improved OCPF decoder walking — the backend now speaks WebSocket in addition to HTTP for renderer ↔ backend communication, enabling bidirectional streaming without the polling/chunked-workaround pattern. The OCPF (Open Collaboration Protocol Format) decoder’s byte-walking logic was also improved for better field extraction and boundary handling.
- MNDP (MikroTik Neighbor Discovery Protocol) decoder — new Conv
→ Decodes + sidebar renderer for MNDP, the MikroTik Layer 2/3
neighbour-discovery protocol carried over UDP/5678 (broadcast).
The decoder walks all known TLV types — MAC Address, Identity,
Version, Platform, Uptime (centiseconds), Software ID, Board,
Unpack, IPv6/IPv4 Address, and Interface Name — and auto-detects
via the same CDP-style byte-heuristic cascade (gated on the
mandatory first TLV being type 1 / MAC Address, length 6, per
Wireshark’s dissector). Registered in
SUPPORTED_DECODER_PROTOS,PROTOCOL_DECODER_HINTS(mndp,mikrotik-neighbor-discovery), andPORT_DECODER_HINTS(5678). - Industrial protocol decoders — Modbus, S7comm, DNP3 (Conv →
Decodes) — three new Conv stream decoders handle SCADA/ICS
traffic: Modbus (function codes 1–23, coils/discretes/registers,
exception responses), S7comm (Siemens S7 communication,
setup, read/write, cyclic, PLC-stop, and diagnostics), and
DNP3 (Distributed Network Protocol, data objects, binary/analog
inputs/outputs, control/cooking). All three are registered in
SUPPORTED_DECODER_PROTOSand auto-detected viaPROTOCOL_DECODER_HINTS/PORT_DECODER_HINTSfor ports 502 (Modbus), 102 (S7comm), and 20000 (DNP3). - Network-control / discovery protocol decoders — CDP, HSRP, LACP,
OSPF, STP (Conv → Decodes + sidebar) — five more Conv decoders
round out the LAN/network-control coverage: CDP (Cisco
Discovery Protocol), HSRP (Hot Standby Router Protocol),
LACP (Link Aggregation Control Protocol), OSPF (Open
Shortest Path First), and STP (Spanning Tree Protocol). All
ship in both
src/backend/decoders/andsrc/ui/decoders/conv/with matching sidebar renderers insrc/ui/decoders/main/, are registered inSUPPORTED_DECODER_PROTOS, and auto-detect via the standardPROTOCOL_DECODER_HINTS/PORT_DECODER_HINTSmachinery. Coverage:tests/test_backend_ospf_decoder.pyandtests/test_backend_stp_decoder.py. - MNDP (MikroTik Neighbor Discovery Protocol) decoder — new Conv
→ Decodes + sidebar renderer for MNDP, the MikroTik Layer 2/3
neighbour-discovery protocol carried over UDP/5678 (broadcast).
The decoder walks all known TLV types — MAC Address, Identity,
Version, Platform, Uptime (centiseconds), Software ID, Board,
Unpack, IPv6/IPv4 Address, and Interface Name — and auto-detects
via the same CDP-style byte-heuristic cascade (gated on the
mandatory first TLV being type 1 / MAC Address, length 6, per
Wireshark’s dissector). Registered in
SUPPORTED_DECODER_PROTOS,PROTOCOL_DECODER_HINTS(mndp,mikrotik-neighbor-discovery), andPORT_DECODER_HINTS(5678). - Artifacts store overhaul (replaces Keystore) — the former
Keystore panel was rebuilt into a broader Artifacts store
that now handles arbitrary file artifacts alongside keys and
credentials. Artifacts are typed (
wifi-wep,wifi-wpa-psk,wifi-pmk,generic-secret,file, etc.), each carries a name/description/timestamp, and the panel was reorganised with better grouping, search, and bulk-operations support. - Conv UX improvements — image no-op / partial data handling, decoder dropdown selection, protocol autodetection fix — the Conv output renderer now correctly handles image streams with no-op transforms and partial data without cutting off renders. The Decodes dropdown selection UX was improved for faster protocol switching. Protocol autodetection was tightened to reduce false positives on non-protocol traffic that happened to look like valid protocol headers.
- Stats → Conv “jump to protocol decoder” context menu — right- clicking an artifact on the Stats tab now offers a context-menu entry that jumps directly to Conv’s Protocol Decoder view on the stream that produced the artifact, removing the manual “find the stream, open Conv, pick the decoder” workflow.
- Host Data view alignment overhaul — the Host Data panel’s long-standing alignment issues were fixed end-to-end: column borders, header rows, and per-row field renderers now line up consistently across resizes and across the panel’s collapsible groups.
- Removed deprecated “Use LLM” checkbox — the legacy
settings.capture.useLlmfield and its associated UI control were removed. LLM integration is now always available when an LLM provider is configured; the checkbox was redundant with the provider/model selection controls. - Summarization pipeline improvements — internal refinements
to the LLM summarization pipeline in the data tools panel
(
src/ui/panels/data-tools-llm-summarizer.js,src/ui/panels/data-tools-panel.js) for better chunking and output assembly. - PCAP import cleanup hardening — while zeroing capture state in preparation for a new PCAP import, log output is now suppressed and external function calls to non-localhost sites are blocked, so a partially-zeroed state can’t leak data or fan out telemetry to remote endpoints mid-transition.
🐛 Fixes
- Duplicate data in the artifacts store — fixed a bug where re-running a capture could double-count artifact entries in the store; the store now correctly reuses existing entries by key instead of creating duplicates.
- Conv search/replace alignment — the search-and-replace bar in Conv is now visually and functionally aligned with its manual carve counterpart, with consistent state tracking across both paths.
- Autoselectable decoder restrictions tightened in Conv — the heuristic that lets Conv auto-pick a decoder was further restricted so plain-text and other non-protocol streams are no longer misrouted to a specialised decoder when the bytes happen to match a header shape.
- Dropdown transparency bug — fixed a regression where the LLM/Settings dropdowns rendered with a transparent background against certain themes; they now use the standard theme surface fill.
- PCAP import cleanup hardening — while zeroing capture state in preparation for a new PCAP import, log output is now suppressed and external function calls to non-localhost sites are blocked, so a partially-zeroed state can’t leak data or fan out telemetry to remote endpoints mid-transition.
v2.7.1736 - 2026-09-01
Type: minor
Session library import — bring exported sessions back into the picker without hunting for the sessions directory.
✨ Features
- Session picker → Import — the saved-sessions picker now has an
Import button next to New Session. Clicking it opens a file
dialog, validates the selected file against the expected
PacketSnitch session structure, prompts for a destination name, and
writes the session into the library so it shows up in the list and
can be opened like any other saved session. This replaces the
manual “find the sessions directory and copy the file in” workflow.
The canonical import format is the gzipped-BSON
.psbproduced by the existing Export action; legacy.pss/.pss.gz/.jsonexports are still accepted for backward compatibility but surface a deprecation notice prompting the user to re-export as.psb. The validation lives in a new dependency-free module (src/session-format.js) that both the main-process import handler (src/main.jssession-importIPC) and the renderer’s session-name prompt round-trip consume, so an invalid file is rejected with a precise error (missinghostmap, empty host map, non-array host entry, non-object packet, etc.) before anything is written to disk. The preload bridge exposeswindow.sessionsapi.importFromFile(),onImportPromptName(callback), andsendImportNameResult(name), and the picker panel (src/ui/panels/session-picker.js) reuses the existing session-name dialog for the destination prompt.
v2.7.1735 - 2026-08-31
Type: minor
Theme engine overhaul, OpenRouter LLM provider, Conv data transformations, ISO 8583 decoder, and a VirusTotal results panel that remembers every lookup — plus the usual round of polish.
✨ Features
- OpenRouter LLM provider (Settings → LLM) — PacketSnitch now ships a
first-class OpenRouter.com integration alongside the existing Ollama
surface. The Settings → LLM sub-tab exposes a Provider dropdown
(Ollama / OpenRouter) that drives the rest of the UI: choose a
provider, then pick a model from that provider’s bundled catalog.
OpenRouter ships with five defaults (
openai/gpt-4o-mini,openai/gpt-4o,anthropic/claude-3.5-sonnet,google/gemini-pro-1.5,meta-llama/llama-3.1-405b-instruct) inconfig/models.jsonunder a newopenrouter.defaultModelsblock, the main process seeds<userData>/config/models.jsonon first run via the newensureModelsLibraryFileExistsopenrouter branch, and the renderer reads it through the newopenrouter-apipreload bridge (listModels/getModel/getStatus+ a 30scachedOpenRouterLastLookupdiagnostic probe so the zero-cost reachability check never gets conflated with a real generation). Generation routes through a dedicatedgenerateOpenRouterhandler in src/llm.js that talks tohttps://openrouter.ai/api/v1/chat/completions, normalises the OpenAI-compatible response back into the Ollama shape the rest of the renderer already speaks, and reusesgetFetchForProviderso the per-timeout dispatcher cache (openrouterDispatcherCachein src/main.js) lines up with the existing Ollama surface. The API key lives insettings.apiKeys.openrouterApiKey, the selected model insettings.llm.openrouterModel, the timeout insettings.llm.openrouterRequestTimeoutSeconds, and the per-call{ maxTokens, temperature, think }overrides from the 2.6 cycle flow through the same provider-agnostic channel. - Theme engine overhaul (Settings → Themes + storefront) — the
theme engine was rebuilt around a new shared
src/ui/common-frontend.jshelpers module plus a unified theming surface in src/main.js. The catalog now renders license tiers (developer / standard / pro) inline with badges and supports in-storefront previews sourced from per-themetheme.jsonpreviews with a fixed 400×250 aspect ratio (themes/smokeshow.jsonand the other theme JSONs were updated to drop the legacypreviewworkaround). A newthemeapi.getLicenseTierpreload bridge ships on top of the existingthemes-catalogIPC, the keystore panel surfaces the new tier badge, and the developer tier is wired end to end so a developer-licensed theme unlocks correctly on first launch. Themes now group consistently in the storefront (by license, not by rawtheme.jsonkey), the Sandbox-mode banner readspaddleEnvwith the legacysandboxflag fallback, and the 400×250 preview fetch retries once on a transient undici failure with a “preview unavailable” fallback. Tests:tests/themes_subtab.test.js,tests/test_catalog_portal.py,tests/test_catalog_admin_api.py,tests/test_catalog_list_themes.py,tests/test_catalog_paddle_poller.py. The newtests/test_catalog_listable_gate.pylock-in prevents a theme that the catalog hasn’t marked as listable from appearing in the storefront at all. - ISO 8583 financial protocol decoder (Conv → Decodes) — a new ISO
8583 decoder ships in both the backend
(
src/backend/decoders/iso8583.py) and the Conv decoder (src/ui/decoders/conv/iso8583.js). It decodes the standard ISO 8583 message structure: MTI (4 ASCII digits or 2 BCD bytes), primary/secondary bitmap (ASCII-hex or binary), and data elements per the ISO 8583:1987/1993 field definitions. Supports LLVAR/LLLVAR length prefixes in both ASCII and binary modes, transparently strips 2- or 4-byte TPDU/message-length framing prefixes common in ISO 8583 over TCP, and rejects false positives (e.g. HTTP text that happens to start with 4 ASCII digits) by validating the BCD MTI against the known MTI table and requiring the first data field to parse. Auto-detect routes matching traffic (ports 8583, 5000, 5001, 14401) to the new decoder, and the Decodes dropdown now offers “ISO 8583 (Financial)”. Field values that are BCD-packed or otherwise non-printable render as hex in the conv decoder table via the newreadAsciiOrHexhelper and thedata-tools-proto-hexCSS class inrenderProtoDecoderOutput. Two sample captures ship for quick smoke-testing:samples/pcaps/iso8583_ascii_sample.pcapng(port 14401, ASCII-hex bitmap) andsamples/pcaps/iso8583_bin_sample.pcapng(port 14401, binary bitmap). Coverage:tests/iso8583_conv_decoder.test.js(8 tests) andtests/test_backend_iso8583_decoder.py(6 tests). - Data transformations in Conv (
Invert / Endian / Bit-order / Transpose) — the Conv output panel now exposes a dedicated Data Transformations block (#data-tools-transform-rowin src/index.html, wired through src/ui/data-transformations.js- new
applyDataToolsTransformsToOutput/resetDataToolsTransformshandlers in src/ui/main-frontend.js). Four composable transformations are supported, applied in order: Invert data (full byte-sequence reversal), Endianness swap (16-bit or 32-bit word reversal — chosen via a Word radio), Bit order (reversal of every bit within every byte), and Transpose (read the bytes row-major into a matrix with the user-specified column count and emit them column-major). A Reset Output button restores the untransformed Conv input bytes; Apply to Output re-renders the panel and writes aConv output transforms applied transforms=…line to the activity log. The transformations are pure functions, exported and tested intests/data_transformations.test.js(covers reverse, swap, bit-order, transpose, error paths, and the composedapplyDataToolsTransformsrunner). The Conv input itself is never modified — only the displayed output.
- new
- Threat Intel → VirusTotal results panel (history + multi-result
cards) — the Threat Intel sub-tab in
src/ui/panels/subnet-calculator-panel.js
now keeps every VirusTotal lookup across the session and renders
them as stacked result cards instead of overwriting a single
card. The new
virustotalResults[]array is plumbed through the session save/load round-trip (newvirustotalResultskey on the threat-intel snapshot), seeded from a single historicalthreatIntelState.virustotalvalue when a legacy session is restored, and the IPSum / Tor cards are now hidden by default (#subnet-ti-ipsum-card/#subnet-ti-tor-cardwithsetOptionalThreatIntelCardVisibility) so the panel only shows reputation sources the analyst actually consulted. The VirusTotal response object also now exposes the full attribute surface (size,type_description,type_extension,type_tag,magic,tags,type_tags,magika,filecondis,times_submitted,first_submission_date,last_submission_date,last_modification_date,last_analysis_results,sigma_analysis_results,sigma_analysis_stats) plus therawpayload so the renderer never has to guess. The stats block also surfacesconfirmed_timeoutandfailure/type_unsupportedcounters that older builds dropped, andattributes.namesis truncated to a 3-entry sample so legacy renderers don’t explode the card with hundreds of aliases. The newanalysislookup type routesGET /analyses/{id}andGUI: /file-analysis/{id}so analysts can walk the full analysis chain after uploading a sample. Coverage: the extendedtests/threat_intel_virustotal_ui.test.jsplus the new analysis-id branch intests/test_backend_compile.py/tests/test_backend_json.py. - Provider-agnostic
llm:generateIPC channel — the LLM generation IPC handler is now exposed under the canonicalllm:generatechannel name, with the legacyollama:generatechannel kept as a backward-compat alias. The handler readssettings.llm.providerand dispatches to the active provider’s registered handler in src/llm.js (Ollama forprovider === "ollama", OpenRouter forprovider === "openrouter"), so the channel name no longer falsely implies an Ollama-only path when OpenRouter is selected. Thellmapi.generatepreload bridge in src/preload.js now callsllm:generate; the handler in src/main.js is a sharedhandleLlmGenerateRequestfunction registered under both channel names.
🐛 Fixes
- Threat Intel → shorter labels fit smaller windows — the Threat Intel sub-tab’s IPSum / Tor / VirusTotal card titles were shortened so the three reputation cards no longer overflow on default window widths. Wired through src/ui/panels/subnet-calculator-panel.js and src/index.html.
- Theme-engine UX plumbing — the theme engine now controls the
storefront UX more directly: per-theme
theme.jsonpreviews are loaded with a fixed aspect-ratio container (no more stretched thumbnails), the license-tier badge in Settings is consistent with the storefront rendering, and the developer-license path works end-to-end on first launch. - Host Data info-pane grouping — the
current-stream-packet,current-filtered-packet, andtimestampreadouts moved out of the top toolbar /welcomepane and now live under a dedicated General Information block (#general-info-head+#packet-readouts.packet-readoutsin src/index.html) inside the active-recon scroll, so the analyst no longer loses the timestamp when the toolbar overflows. CSS in src/assets/css/style.css (#packet-readouts.packet-readouts) styles the new block consistently with the existing#protocols/#compressiongroups.
v2.6.1629 - 2026-08-20
Type: minor
✨ Features
- OpenSSH keystroke-timing decoder (Crypt → OpenSSH sub-tab) — a full keystroke-reconstruction engine now lives under
src/ui/decoders/ssh-keystrokes/(index.js+markov.js,calibration.js,auto-calibrate.js,boundary-warmstart.js,chunk-label.js,chunker-slider.js,clock-skew.js,backspace-detect.js,compression-heuristic.js,padding-detection.js,session-confidence.js,ssdeep.js,truth-align.js,score-envelopes.js,markov-loader.js,worker.js, plus anexport/barrel). Given a sequence of inter-packet delays from a TCP stream on port 22/2222 the decoder scores each delay against a per-QWERTY-digraph Gaussian model (src/data/qwerty-model.json, sourced from Monrose & Rubin / Song, Miller & Stahie / Killourhy & Maxion), runs an N-best Viterbi over printable ASCII to produce the most likely keystroke strings, and renders a Plotly delay histogram. The pipeline is pure (no DOM, no Plotly, no fs/path) so it exercises from Jest and bundles cleanly through webpack. The bundledshell_corpus_sorted.txt(a sorted, de-identified shell command corpus) is loaded by the main process at startup viaworker_threadsand pre-cached intouserData; the renderer pulls it through the newmarkovapipreload bridge (getUserDataDir/getStatus/getModel/train/getCachedShellMarkov) and the corpus path is resolved across source, dev-bundle, and packaged-install layouts inresolveMarkovCorpusPath(src/main.js). The newopensshapipreload bridge exposesloadQwertyModel+decode, andllmapi.generatelets the renderer ask the main process to forward prompts to the configured Ollama backend with per-call{ maxTokens, temperature, think }overrides. The OpenSSH sub-tab ships a full settings surface (#crypt-openssh-*controls insrc/index.html) wired through the newkeystrokesettings block (markovMinCommandLength,concisenessBonusMultiplier) and thellmblock (language,llmWeightPercent,preset,autotuneEnabled), so the analyst can tune the Markov ranking floor, the short-command bonus, and the LLM re-ranking weight without a restart. Covered bytests/openssh_keystrokes.test.js,tests/{auto_calibrate,boundary_warmstart,chunk_label,chunker_max_packet_wiring,chunker_slider,clock_skew,score_envelopes,ssh_backspace_detection,ssh_keystroke_markov,ssh_keystrokes_compression_heuristic,ssh_padding_detection,ssh_session_confidence,ssh_timing,truth_align,weight_envelope_migration}.test.js. - hashes.com reverse-lookup integration (Conv → Hashes) — the Hashes sub-tab now has a dedicated Hash Reverse section (
#data-tools-hash-reverse-*insrc/index.html): paste an MD5/SHA-1/SHA-256/… hash and click Reverse Hash to POST it to hashes.com/en/api/searchand render any plaintext matches, or click Identify Hash Types to GET the/en/api/identifierendpoint and list candidate algorithms. The main process exposes three new IPC handlers —hashes-com:search,hashes-com:identify,hashes-com:diagnostics— coalescing concurrent callers and caching a recent successful probe for 30s (mirroring the VirusTotal diagnostics path), and the preload bridge exposesextractapi.hashesComSearch/hashesComIdentify/hashesComDiagnostics. The Settings tab renders four pills for the endpoint: reachability, key validity, last cost, and last lookup result, with acachedHashesComLastLookuptracker so the diagnostic probe (zero-credit) never gets conflated with a real reverse. The API key lives insettings.apiKeys.hashesComApiKey. Covered bytests/hash_reverse_helpers.test.js,tests/hashes_com_diagnostics.test.js, andtests/hashes_com_identifier.test.js. - Crypt → Hashes sub-tab — the Crypt workspace now opens on a dedicated Hashes sub-tab (
#crypt-hashes-panel,CRYPT_HASHES_SUBTABinsrc/ui/main-frontend.js) that mirrors the Conv → Hashes panel: it reads the current value of#data-tools-hash-input-readingand renders all nine digests (MD5 / SHA-1 / SHA-256 / SHA-384 / SHA-512 + SHA3-256 / SHA3-512, RIPEMD-160, Whirlpool) via the newrenderCryptHashesFromConvInputhelper, so analysts can inspect digests without flipping workspaces. Hashes is now the default Crypt sub-tab on a fresh install. The reverse-lookup UI also gained a Cross Reference Hash button alongside the existing one. - Lazy packet hydration + backend reuse at startup — the renderer no longer materialises the full capture into memory up front.
capture-store.jswas refactored to build an in-memory store (storeIdnowmem-<ts>-<rand>, no disk write of the.packets.ndjsonfile), and a newbackendEarlyYieldPacketThresholdsetting (~5000 packets) tells the renderer to load the first backend snapshot and then defer until the backend completes, at which point it does a clean full swap — no expensive incremental merge. On GUI startup the bridge now reuses an already-running backend on the configured HTTP port instead of spawning a new one (tracked byreusedExistingBackendAtStartupinsrc/back-comm.js), eliminating the port-conflict respawn loop that previously churned on large captures. ThebackendPacketChunkSizedefault dropped 2000 → 500 for smoother progressive pushes. Covered bytests/listentries_fast_path.test.js,tests/test_network_metadata.test.js, andtests/wifi_keys_rerun_session_following.test.js. - Per-call LLM overrides + thinking-mode bypass —
ipcMain.handle('llm:generate', ...)(with a backward-compat aliasipcMain.handle('ollama:generate', ...)) now accepts a secondoptionsargument ({ maxTokens, temperature, think }) that overrides the user’smaxSummaryTokens/ temperature defaults per call. The renderer can passthink: falseto disable the model’s internal-reasoning channel — Ollama cloud models likeminimax-m3:clouduse thinking mode by default and can consume the entire response budget in reasoning with nothing emitted asresponse; disabling thinking is the most reliable way to get a structured answer back. The newllmapi.generate(prompt, options)preload bridge exposes this to the renderer. The OpenSSH decoder uses the override path for its multi-field JSON interpretation. - Website moved to its own submodule — the docs site is now a git submodule at
docs/PacketSnitch-Website(added to.gitignoreso the submodule pointer doesn’t pollute the tree), the app homepage inforge.config.jsandpackage.jsonpoints athttps://packetsnitch.com/, and the bundledshell_corpus.txtextraResource inforge.config.jsnow references the sorted corpus (src/data/shell_corpus_sorted.txt).
🐛 Fixes
- Theme preview loading — the theme catalog’s 400×250 preview fetch now retries once on a transient undici failure and surfaces a “preview unavailable” state instead of leaving a broken image card; the sandbox-banner logic correctly reads
paddleEnv(with the legacysandboxflag fallback) so test purchases can never be mistaken for real ones. - Heatmap convergence animation + backend reuse — the heatmap’s convergence leader lines now animate, and the backend is reused across animation passes instead of being continuously respawned (which never took). The Plotly vendored under
src/assets/vendor/plotly-2.35.2.min.jsis loaded only on the OpenSSH sub-tab so it doesn’t slow the main render path. - Backend reclaim no longer drops data on re-runs — the startup reclaim path now stops any current jobs and reuses the running service rather than spawning a conflicting backend; previously the port was held by a stale process and the new backend silently failed to bind.
- Session-bound state survives wifi-keys reruns — a
pendingSessionRerunSnapshot(current session name, filter query, selected host) is taken at the start of a wifi-keys rerun and re-applied when the rerun completes, so the user’s session identity and view state survive the silent background re-run. AsessionExplicitlyClosedflag distinguishes a user-initiated “new capture” from a background rerun so the Save-Session flow prompts for a fresh name only when the user actually closed the session. Covered bytests/wifi_keys_rerun_session_following.test.js.
🧪 Tests
tests/openssh_keystrokes.test.js— end-to-end wiring of the OpenSSH sub-tab,loadQwertyModel,decode, and theopenssh-decodeIPC payload shape.tests/{auto_calibrate,boundary_warmstart,chunk_label,chunker_max_packet_wiring,chunker_slider,clock_skew,score_envelopes,ssh_backspace_detection,ssh_keystroke_markov,ssh_keystrokes_compression_heuristic,ssh_padding_detection,ssh_session_confidence,ssh_timing,truth_align,weight_envelope_migration}.test.js— per-module coverage of the ssh-keystrokes engine.tests/hash_reverse_helpers.test.js,tests/hashes_com_diagnostics.test.js,tests/hashes_com_identifier.test.js— hashes.com search / identify / diagnostics wiring, pill rendering, and the zero-credit diagnostic-vs-real-lookup distinction.tests/listentries_fast_path.test.js,tests/test_network_metadata.test.js— lazy packet hydration + in-memory capture-store refactor.tests/wifi_keys_rerun_session_following.test.js— session-bound state survives a wifi-keys background rerun.tests/test_regex_issue.test.js,tests/packet_timestamp_parse.test.js,tests/decoder_layer1_envelope.test.js,tests/metrics_tab_tracking.test.js(extension),tests/ssdeep.test.js,tests/hash_reverse_helpers.test.js— assorted regressions.
🔧 Improvements
src/settings.jsnow carries thekeystrokeblock (markovMinCommandLength,concisenessBonusMultiplier), the extendedllmblock (language,llmWeightPercent,preset,autotuneEnabled),backend.httpProgressLogMinIntervalMs,debug.backendEarlyYieldPacketThreshold,backend.backendPacketChunkSize(2000 → 500), andapiKeys.hashesComApiKey— all normalised throughnormalizeSettingsso legacy/corrupt values round-trip safely.src/back-comm.jshonoursbackend.httpProgressLogMinIntervalMsto throttle[Bridge] HTTP progress ...activity-log lines without affecting renderer data flow, and reuses an existing backend at startup instead of spawning a conflicting one.src/main.jsresolves the bundled Markov corpus across source, dev-bundle, and packaged-install layouts; precomputes the shell-Markov model at startup in aworker_threadsworker; and exposes themarkov:*,hashes-com:*, andopenssh-*IPC surfaces.src/preload.jsadds themarkovapi,llmapi, andopensshapibridges, extendsextractapiwithhashesComSearch/hashesComIdentify/hashesComDiagnostics, and extendsthemeapiwithgetLicenseTier.
v2.5.2304 - 2026-08-09
Type: minor
✨ Features
- Configurable post-load landing tab (Settings → General) — a new “Open after capture load” preference lets the user pick which workspace opens after a capture finishes loading. Three options are exposed in the General sub-tab as a
<select>(with help text describing each): Host Data (drills into the first packet’s hex/ASCII + protocol tree), Stats (the at-a-glance Capture Statistics / Map / Anomalies overview), and List (the sortable, pcap-ordered packet list). The default is Stats, so a fresh install lands on the overview without forcing the user to click a tab — but every choice is one click away on the General settings panel. Stored asgeneral.defaultTabin the persisted settings (seesrc/settings.js), normalized against a newVALID_DEFAULT_TABSwhitelist so a corrupt or stale value can never strand the user on a blank workspace, and read by the newresolveDefaultLandingTab()/openDefaultLandingTab()helpers insrc/ui/main-frontend.js. The existing session-state restoration path now also falls back to this preference instead of the hard-coded tab, so a session saved before the default was changed still honors the saved choice while a brand-new capture honors the new preference. The new setting is reflected back into the<select>on everysyncSettingsFormFromState()pass, logs aSettings updated defaultTab=…line to the activity log on change (matching the other General settings), and goes through the existingpersistSettingsFromForm()pipeline so the VirusTotal diagnostics cache invalidation, capture-worker resync, theme re-apply, and metrics-save instrumentation all keep working unchanged. - In-app theme catalog (Settings → Themes) — a full storefront experience now lives inside PacketSnitch. The Themes sub-tab auto-fetches a live catalog from the catalog server (default
https://oxasploits.com:9021/) on first open, renders a responsive card grid (name, description, price, 400×250 preview), and exposes a “Buy” button that opens the Paddle-hosted checkout in the user’s default browser viashell.openExternal. Successful purchases deep-link back into PacketSnitch through a newpacketsnitch://checkout-successdeeplink (handler insrc/main.js), where the main process callsreconcileThemeLicenses({ force: true })to pull the newly-licensed theme(s), pre-caches them underuserData/theme-cache/<id>/theme.jsonso they work offline, and broadcastsdeeplink:checkout-successto the renderer with the unlocked theme ids. A 72-hour background recache timer keeps owned-but-not-yet-installed themes warm. The “Sandbox mode” banner above the catalog grid lights up automatically when the server returnspaddleEnv: "sandbox"(or the legacysandbox: trueflag) so test purchases can never be mistaken for real ones. - Catalog & purchase hardening — the catalog server URL, recache interval, and
allowInsecureTlsEndpointsflag are now hard-coded constants insrc/main.js(mirrored insrc/settings.js) so the purchase path can’t be redirected or neutralized via settings edits. HTTPS calls to the catalog server (and the metrics endpoint, which shares the same self-signed cert) attach an undici dispatcher withrejectUnauthorized: falsevia a cachedgetInsecureThemeServerDispatcherhelper. Thethemes-catalogIPC handler now translates undici’s genericfetch failedinto an actionable hint — plain-http://URLs pointing at an HTTPS-only server get a “change the URL to https://” hint, self-signed cert failures with the insecure-TLS flag off get a “enable ‘Allow self-signed certificates for the theme server’ in Settings → Themes” hint, and unreachable hosts get a “verify the URL and your network” hint.fetchWithTimeoutnow logsbegin / ok / faillines (with elapsed-ms, abort status, and a sanitized cause message) to the activity log so transient catalog outages are diagnosable fromactivity-log.txt. The settings-endpoint URL is built withbuildThemeServerUrl(..., { force: "1" })so a “Refresh Catalog” click bypasses the 60-second license-check cache. - The top toolbar Help button opens the PacketSnitch documentation hub at https://packetsnitch.com/docu/ in PacketSnitch’s own in-app browser window — the same child
BrowserWindowthat the previous behaviour used. The main-processdid-create-windowhandler insrc/main.jslocks the child window to whitelisted docs hosts (packetsnitch.com, github.com/oxasploits/PacketSnitch, buymeacoffee.com, virustotal.com), resizes it to 1200×900, strips the menu bar, and tags it with the desktop user-agent. Help is the only in-app link that opens inside PacketSnitch; every other external link (release notes, donate, theme catalog, VirusTotal card, etc.) still routes to the user’s default system browser viashell.openExternalso the user keeps their existing browser session. - New Kerberos 5 (
krb5) Conv decoder (src/ui/decoders/conv/kerberos.js): the Decodes sub-tab now exposes a “Kerberos (krb5)” entry that disassembles AS-REQ/AS-REP/TGS-REQ/TGS-REP/AP-REQ/AP-REP/KRB-ERROR/KRB-PRIV/KRB-CRED messages and surfaces pvno, msg-type, realm, cname/sname, KDC options (with the RFC 4120 bit-numbered flags), till, nonce, etype list, ticket (tkt-vno/realm), and an EncryptedData etype + cipher preview. Auto-detect and protocol/port hints (krb5,kerberos, ports 88/464/750) route matching traffic to the new decoder, and the inline decoder switch insrc/ui/main-frontend.js+src/ui/main-frontend-test.cjsis updated to match. - New Conv Decodes dropdown entries + stream decoders for DNS, SNMP, DHCP, and DHCPv6 — the Decodes protocol selector (
#data-tools-proto-selectin src/index.html) now offersDNS,SNMP,DHCP, andDHCPv6alongside the existingKerberos (krb5)/ SIP / SMB entries, and each one is backed by a real Conv stream decoder instead of just a sidebar table. The new files live insrc/ui/decoders/conv/{dns,snmp,dhcp,dhcpv6}.js, are re-exported from thesrc/ui/decoders/conv/index.jsbarrel, are routed in thedecodeWithSelectedProtocolswitch in src/ui/panels/data-tools-panel.js, and are registered withSUPPORTED_DECODER_PROTOSin src/ui/decoders/conv/mime-maps.js so the auto-detect path can pick them. Behaviour summary:dns(port 53) — splits TCP-framed DNS into 2-byte length-prefixed messages, walks each message’s header (id, QR/Opcode/AA/TC/RD/RA/AD/CD, rcode), and parses question + answer/authority/additional sections: label-sequence decoding (RFC 1035 §4.1.4 with compression-pointer dereferencing across message boundaries), QTYPE/QCLASS lookup for A/AAAA/CNAME/MX/NS/TXT/SOA/PTR/SRV/HINFO, and a per-RR resource-record tree showing name/type/class/TTL/rdlength plus the typed rdata preview (A → IPv4, AAAA → IPv6, CNAME → target, MX → preference+exchange, NS → nsdname, TXT → character-string list, SOA → mname/rname/serial/refresh/retry/expire/minimum, SRV → priority/weight/port/target). IPv4/IPv6 A/AAAA rdata is rendered with the existing dotted/colon formatting helpers.snmp(ports 161/162) — consumes BER-encoded SNMPv1/v2c/v3 PDUs (using the sharedparseAsn1Length+decodeBerFromBytesin src/ui/decoders/conv/asn1.js / ber.js), walks the SEQUENCE-wrapped message, and surfaces the version (SNMPv1/SNMPv2c/SNMPv3), community string, request-id, error-status (noError/tooBig/noSuchName/badValue/readOnly/genErr/noAccess/wrongType/wrongLength/wrongEncoding/wrongValue/noCreation/noSuchObject/noSuchInstance/endOfMibView/inconsistentValue/resourceUnavailable/commitFailed/undoFailed/authorizationError/notWritable/inconsistentName), error-index, and a flattened VarBind tree with OID, named-MIB lookup (1.3.6.1.2.1.1.1.0→sysDescr.0, etc.), and a hex-or-textual value preview.dhcp(ports 67/68) — walks the RFC 2131 BOOTP/DHCP header (op, htype/hlen, hops, xid, secs, flagsBROADCAST, ciaddr/yiaddr/siaddr/giaddr, chaddr, sname, file, magic cookie) and then streams the DHCP option list in declaration order, decoding every option from the registry (subnet-mask, router, time-servers, name-server, log-server, cookie/server, lpr-server, impress-server, rlp-server, host-name, boot-file-size, merit-dump-file, domain-name, swap-server, root-path, extensions-path, ip-forwarding, non-local-source-routing, policy-filter, max-datagram-reassembly-size, default-ip-ttl, interface-mtu, all-subnets-local, broadcast-address, router-discovery, router-solicitation, static-routes, trailer-encapsulation, arp-cache-timeout, ethernet-encapsulation, tcp-default-ttl, tcp-keepalive-interval, tcp-keepalive-garbage, nis-domain, nis-servers, ntp-servers, vendor-specific, netbios-name-servers, netbios-dd-server, netbios-node-type, netbios-scope, x-window-fs, x-window-dm, requested-ip, lease-time, option-overload, message-type →DHCPDISCOVER/DHCPOFFER/DHCPREQUEST/DHCPDECLINE/DHCPACK/DHCPNAK/DHCPRELEASE/DHCPINFORM, server-id, parameter-request-list, message, max-message-size, renewal-time, rebinding-time, vendor-class-id, client-id, rapid-commit, plus any vendor-specific / unknown option as raw hex), surfacing the parsed fields as a tree.dhcpv6(ports 546/547) — RFC 8415/3315 relay + server/client message decoder: parses the 1-byte msg-type (SOLICIT/ADVERTISE/REQUEST/CONFIRM/RENEW/REBIND/REPLY/RELEASE/DECLINE/RECONFIGURE/INFORMATION-REQUEST/RELAY-FORW/RELAY-REPL) + 3-byte transaction-id header, then walks the TLV option stream (client-id, server-id, IA-NA, IA-TA, IAADDR, ORO, preference, rapid-commit, unicast, status-code, vendor-class, vendor-opts, interface-id, reconf-msg, reconf-accept, dns-servers, domain-search-list, sntp-servers, fqdn, leasequery, + recursive nested IAs and relay message options) with proper 16-bit option-length handling. Status-code sub-options are decoded intoSuccess/UnspecFail/NoAddrsAvail/NoBinding/NotOnLink/UseMulticast/NoPrefixAvail/etc.PROTOCOL_DECODER_HINTSandPORT_DECODER_HINTSin src/ui/decoders/conv/protocol-hints.js are extended so the inline decoder switch insrc/ui/main-frontend.js+src/ui/main-frontend-test.cjs, auto-detect (autoDetectProtoFromBytes), and thedata-tools-proto-select<select>populate the right protocol the first time a user opens Decodes on a DHCP/SNMP/DNS stream. The new tests live intests/{dns,snmp,dhcp,dhcpv6}_conv_decoder.test.jsand follow the same pattern astests/kerberos_conv_decoder.test.js(wiring + registry + hints + round-trip fixtures).
- Notes tab is now wired into the Summary tab: every note created on the Notes tab is automatically reflected on the Analysis/Summary tab under a clear “Inferred Data (from Notes)” heading, so the analyst never has to copy/paste observations between the two tabs. Each note carries a new “Mark as verified data (concrete)” checkbox in the Notes editor; toggling it moves the note to a separate “Verified Notes (from Notes)” heading on the Summary tab so concrete analyst-confirmed facts are visually distinct from inferred observations. The flag persists across session save/load.
- Better decodes stream following in Conv (
src/ui/decoders/conv/{index,smb,smb-helpers}.js+src/ui/panels/data-tools-panel.js): the Conv Decodes subtab now has a dedicated SMB follow-stream path that walks SMB2 read/write transactions, presents a per-message tree of headers, file content, and offsets, and reuses the unified stream-stack assembly used by other decoders. The new SMB helpers are extracted intosmb-helpers.jsso the inline decoder code stays small and testable. - Wifi / 802.11 decryption in the Crypt tab — the Crypt workspace now has a dedicated Wifi subtab (
crypt-wifi-panelin src/index.html) backed by a brand-new 802.11 backend decoder (src/backend/decoders/wireless_80211.py + src/backend/decoders/wpa2_ptk.py). The decoder auto-detects Dot11 / RadioTap frames, walks management/data/control sub-types, and surfaces SSID, BSSID, channel, frame type/subtype, cipher, and crypto metadata. When a WPA2 4-way handshake is present in the capture, the backend derives the per-session PTK (PBKDF2-HMAC-SHA1 PMK → PRF-384 PTK per IEEE 802.11i §8.5.1) and uses the TK portion to decrypt CCMP data frames; WEP and pre-computed PMK keys are also supported. New sample capturessamples/pcaps/wifi-Coherer-Induction.pcapandsamples/pcaps/wifi-Coherer-Induction-dec.pcapship with the release for quick smoke-testing. Wifi keys are managed directly from the Crypt tab (#crypt-wifi-keystore-list) with WPA-PSK / WEP / PMK key-type selector, sent to the backend via a newsetBackendWifiKeysIPC, and the backend is auto-rerun in the background so decrypted frames flow into the rest of the workspace without a manual reload. A “decryptable with my keys” filter and SSID/BSSID substring filters help analysts cut straight to decryptable traffic, and the session keystore is the source of truth — keystore entries of typewifi-wep/wifi-wpa-psk/wifi-pmkare auto-collected and shown in the keystore sub-list. - Conv Decodes dropdown entries for EPMAP, LLMNR, NBNS, NBDGM (NetBIOS Datagram), plus upgraded LDAP — the Decodes protocol selector now offers
EPMAP(DCE/RPC endpoint mapper),LLMNR(link-local multicast name resolution),NBNS(NetBIOS name service), andNBDGM(NetBIOS datagram service) alongside the existing entries. The LDAP decoder has been substantially upgraded to handle more message types, response parsing, and tree rendering. All four new decoders live in their own files under src/ui/decoders/conv/ (epmap.js,llmnr.js,nbns.js,nbdgm.js) and are re-exported from theindex.jsbarrel; auto-detect and protocol/port hints route the right traffic automatically. Behaviour summary:epmap— walks the DCE/RPC endpoint mapper (EPM) request/response shape and surfaces the tower/UUID/vers/rhs/flags fields plus the inquiry/insert/delete/replace lookup types.llmnr— disassembles LLMNR (RFC 4795) queries and responses, including the QR/Opcode/C/TC/T/Z/RCODE bits, question/answer/authority/additional sections with typed rdata preview (A/AAAA/PTR/CNAME/HINFO).nbns— parses NetBIOS name-encoding (32-byte half-ASCII label) and the RR-type fields (NB, NBSTAT, GENERAL-NAME-SERVICE, etc.), surfacing each entry as a flat list with name/type/class.nbdgm— NetBIOS datagram service (RFC 1002 §6) decoder: walks the 8-byte header (msg-type, datagram-id, datagram-length, packet-offset, source-name, destination-name) and surfaces both broadcast and unicast datagram types.ldap(upgraded) — improved search/filters/entries/attribute rendering; entry attributes now surface as a typed tree (DN/objectClass/cn/sn/etc.) with raw bytes for opaque values.
- Stats → Anomalies sub-tab — the Stats tab now has a third sub-tab alongside Statistics and Map called Anomalies (src/ui/panels/stats-panel.js). It surfaces four structured anomaly detectors running over the loaded capture:
- Portscans (
detectStatsAnomaliesPortscan) — flags a single source sweeping many destination ports in a short window (configurable per-source threshold) and renders each finding with source IP, count, and the targeted port list. - Brute-force login bursts (
detectStatsAnomaliesBruteForce) — detects repeated failed authentication attempts to a single host on ports 21 (FTP), 22 (SSH), 23 (Telnet), 25 (SMTP), 110 (POP3), 143 (IMAP), 3389 (RDP), 5900 (VNC), and 389/636 (LDAP) with a per-destination, per-service rolling window. - Baseline packet-length / per-minute outliers (
detectStatsAnomaliesBaselineOutliers) — computes rolling per-minute packet-count and average length baselines and flags minutes that drift more than a configurable standard deviation from the mean. - High-entropy cleartext payloads (
detectStatsAnomaliesEmbeddedContent) — flags outbound payloads that are both unencrypted and above an entropy threshold (potential covert channels). - Findings render as click-to-filter cards in
renderStatsAnomaliesPanel(clicking a card applies the matching filter expression) and the implementation shares the samedetectProtocolAnomaliesengine with the Threat Intel sub-tab so the two views never disagree.
- Portscans (
- Session Threat Score (Threat Intel sub-tab) — the Threat Intel sub-tab now leads with a Session Threat Score card (
#subnet-ti-score-cardin src/index.html, logic in the new src/ui/panels/threat-intel-scorer.js module). The card shows a 0-100 score with banded pill (Clean/Low/Medium/High/Critical), a color-graded weight breakdown of every contributing indicator (IPSum hits, Tor exit nodes, VirusTotal malicious / suspicious verdicts, high-entropy cleartext, portscan / brute-force / baseline outliers from the Stats Anomalies tab, public-IP / domain / URL / hash counts, and the current Conv input entropy), and a “Capture Footprint” summary with public IPs / unique domains / URLs / registered hashes / reputation lookups / protocol anomalies. Three actions live on the card: Recompute (re-derive the deterministic score), Get LLM Assessment (asks the active LLM to summarize the breakdown into a short analyst narrative plus up to 5 concrete next actions), and Send to Notes (appends the breakdown to the active Notes tab as a structured note). Every per-target lookup in the Threat Intel sub-tab feeds the next recompute, so the score evolves as the analyst does more lookups. AglobalThis.__THREAT_INTEL_DEBUG__toggle is exposed for in-renderer debugging. - Save Report… submenu on the packet context menu — saving the analysis summary moved out of the toolbar and into the right-click context menu under a new
Reports...branch (#ctx-reports-submenu/#ctx-reports-branchin src/index.html, wired in src/ui/fragments/convert-context-menu.js). The submenu exposes four formats — Save Report (Markdown), Save Report (Text), Save Report (HTML), and Save Report (PDF) — backed by the newsaveSummaryFromContextMenu(format)handler and the dedicatedsaveSummaryAsPdf()PDF path in src/ui/main-frontend.js. When the LLM is enabled, a final distillation pass over the assembled Markdown (dedupe + chronological sort + importance re-rank) runs before the save dialog opens so the file the analyst sees in the dialog is the cleaned-up version. If the LLM is disabled, fails, or the report is too short to warrant a pass, the distiller transparently falls back to the un-distilled report. A newSUMMARY_DISTILL_SKIP_EMPTY/SUMMARY_DISTILL_ERRORstatus surface keeps the activity log honest about which path was used. The PDF path reuses the same self-contained HTML the HTML exporter builds and hands it to Electron’s built-inwebContents.printToPDFvia the newsave-pdf-reportIPC in src/main.js, so no third-party PDF dependency is required and the PDF + standalone HTML are visually identical. The Summary workspace header also gets a dedicated Save as PDF button (#summary-save-pdf-btnin src/index.html, wired in src/ui/panels/summary-panel.js). - WEP (Wired Equivalent Privacy) decryption in the Crypt → Wifi sub-tab — the 802.11 decoder in src/backend/decoders/wireless_80211.py now recognizes
Dot11WEPframes (in addition to CCMP / TKIP) and decrypts them with the matchingwifi-wepkeystore entry. The new_wepDecrypt/_wepPlaintextLooksValidhelpers take the proper WEP body (3-byte IV + 1-byte KeyID + ciphertext + 4-byte ICV), RC4-decrypt it viacryptography.hazmat.decrepit.ciphers.algorithms.ARC4(with a legacycryptography.hazmat.primitives.ciphers.algorithms.ARC4fallback for older installs), verify the standard CRC-32 ICV leniently (real WEP pcaps in the wild often have a corrupt or zeroed ICV), and gate the “ok” verdict on a structural LLC/SNAP + valid-EtherType sanity check so the wrong key never produces a false positive. Supported key sizes: WEP-40 (5 bytes), WEP-104 (13 bytes), WEP-128 (16 bytes) —_resolveWepKeyaccepts all three via the samewepKeyHexfield. A newsamples/pcaps/wep-A4-81-53-B4-CF.pcapsample ships with the release (BSSIDc0:4a:00:80:76:e4, WEP-40 keyA4:81:53:B4:CF) so analysts can smoke-test the WEP path end-to-end. Coverage:tests/test_backend_wireless_80211_wep.py(RC4 parity, sanity-check rejects wrong key, decryptWifiPayload WEP returns the same{ok, plaintextHex, algorithm, bssid}shape as the CCMP path) andtests/test_backend_json.py::test_backend_decrypts_wep_capture_with_hex_key(end-to-end bridge → backend → decrypted 802.11 frames on the matching BSSID). - Wifi keys round-trip through sessions — saved sessions now carry the 802.11 wifi keys alongside the rest of the keystore, and on session restore the bridge re-sends them to the backend so re-opening a wifi capture still decrypts 802.11 frames without manual re-entry. The renderer-side state machine (
wifiKeysRerunInFlightflag in src/ui/main-frontend.js) plus the bridge-side--wifi-keys-fileplacement fix in src/back-comm.js ensure the silent re-rerun after a successfulsetBackendWifiKeysactually triggers a fresh backend pass and that pass actually produces decrypted output. Covered end-to-end by the newtests/wifi_keys_rerun_path_mode.test.jsandtests/wifi_keys_legacy_spawn_placement.test.js.
🐛 Fixes
- “Restore defaults” button is now confirmable, not destructive — clicking the Restore defaults button in Settings no longer immediately wipes every setting. It opens a new in-app confirmation dialog (
#settings-reset-confirm-dialoginsrc/index.html) styled like the other confirm overlays (“Restore Defaults” title + descriptive copy: “This will reset all settings to their default values. Any unsaved changes will be lost. Continue?”) with explicitRestore/Cancelbuttons, focus auto-moved toRestoreon open, and full keyboard support (Enter = continue, Escape = cancel) via the newrequestSettingsResetConfirm/resolveSettingsResetConfirmhelpers insrc/ui/main-frontend.js. If the DOM nodes are missing (e.g. very old markup) the helper transparently falls back towindow.confirmso the destructive action can never silently fail to confirm. The status line now readsRestore defaults canceled.when the user backs out, andDefaults restored.(plus an activity-log entrySettings restored defaults) on success; both branches go through the existingpersistSettingsFromForm({ resetToDefaults })path that invalidates the VirusTotal diagnostics cache when the saved key changes, re-syncs capture workers, re-applies the theme, and persists the clonedDEFAULT_SETTINGSsnapshot. A newresetToDefaults: true|falsemetric field is included on every settings-save event so analytics can distinguish a real save from a defaults restore. CSS for the dialog lives alongside the other confirm overlays insrc/assets/css/style.css(#settings-reset-confirm-dialog/#settings-reset-confirm-description). - Fixed decoders bug for single block decodes: when a stream contains exactly one reassembly block, Conv Decodes now correctly feeds that block through the decoder pipeline instead of bailing out. Covered by
tests/conv_decodes_stream_stack.test.js. - Inline decoder wiring (
src/ui/main-frontend.js) now matches the dropdown entries — picking a protocol from the inline switch honours the same detection and hints path as the Conv Decodes subtab. - Wifi rerun no longer drops decryption on the second run — the legacy spawn path in
src/back-comm.jspreviously staged the per-jobwifi-keys.jsonfile insidejobOutputDirand then wipedjobOutputDirimmediately before launching the backend, so the backend always saw a missing--wifi-keys-fileand silently skipped 802.11 (AES-CCMP) decryption. The keys file is now staged intestcaseOutputDir(a sibling ofjobOutputDir) so the wipe can’t touch it, gets a unique per-job filename (wifi-keys-<jobId>.json), and the bridge cleans it up after the backend process closes. The renderer-side rerun fix inprocessBackendJsonPathPayload(honouringwifiKeysRerunInFlight) is now actually effective because the backend actually produces decrypted data again. Regression coverage intests/wifi_keys_legacy_spawn_placement.test.js. - All-Hosts sentinel survives across snapshot refreshes — picking the
0.0.0.0“All Hosts” virtual option in the Host Data target-host dropdown no longer collapses to a single host after a backend progressive snapshot or rerun. The sentinel value is now preserved bylegacyAllHostSentinelplumbing in the renderer so navigation stays bound to the full packet set even when the snapshot pipeline rewrites the host list. Regression coverage intests/legacy_all_host_sentinel.test.js. - Decrypted 802.11 frames no longer masquerade as “WIFI” in the List → App Protocol column — TCP/UDP/ICMP packets recovered from a decrypted Wi-Fi frame used to be relabelled
WIFIin the List panel’s App Protocol column because the backend prepended"WIFI"topacket.decoded_protocolsand the renderer added every decoded name (link-layer included) to its app-protocol candidate set. Two coordinated changes fix this: (1) the backend in src/backend/snitch.py no longer prepends"WIFI"— link-layer identity is already conveyed vialink.proto = "IEEE 802.11", and leaving the transport/app-layer values alone keeps the renderer honest about what was actually decoded inside the stripped 802.11 payload; (2) the renderer in src/ui/panels/list-panel.js now defensively filters link-layer protocol names (WIFI,IEEE 802.11,ETHERNET,LINUX COOKED,LINUX COOKED V1/V2,FRAME,LINK,RAW,LOOPBACK,NULL,TUN,TAP) out ofcollectDecodedProtocolNamesso a legacydecoded_protocolspayload (e.g. an older hosts.json) can’t trick the column into re-labelling decrypted TCP/UDP frames. Decrypted HTTP, SSH, DNS, … flows now surface the real application protocol. Regression coverage intests/list_panel_app_protocol.test.js. - Aggressive dedupe in the Summary distillation + compaction LLM prompts — the export-time distillation pass (
buildSummaryDistillPromptin src/ui/main-frontend.js) and every analysis-compaction run (runAnalysisCompaction) used to ask the LLM to “deduplicate” in a single short sentence, which the LLM interpreted as “remove exact duplicates” — so the same fact restated in slightly different words (e.g. “the host at 10.0.0.1 reached out to example.com” vs. “10.0.0.1 talked to example.com”) survived into the running summary and the saved report, and the same observation accumulated rewrites every time a new blurb was folded into the compacted summary. The fix promotes dedupe to the most important rule in the distillation prompt (AGGRESSIVE DEDUPE) and adds explicit sub-bullets: collapse every duplicate into a single canonical statement, delete the rest of the copies entirely (don’t rephrase them), and — when a fact is summarised at a high level in the Capture Statistics section AND described in an LLM blurb — keep the more detailed version in the LLM-blurb / per-stream section and remove the high-level restatement from the Capture Statistics section. The compaction prompt now carries its ownDEDUPLICATErule that covers both (a) within the previously compacted summary and (b) across the new blurbs, and the per-streamwriteSummaryFromLLMprompt carries a closing “Never rephrase a fact that is already in the running summary” instruction so the blurbs that feed compaction are already deduped before they ever reachrunAnalysisCompaction. A reader should never feel that the same observation is being repeated in the consolidated Summary tab or in the exported report. Locked in by five new test cases in tests/summary_stats_weaving.test.js under the newaggressive dedupe in summary LLM promptsdescribeblock.
🧪 Tests
- New
tests/themes_subtab.test.jsexercises the renderer-side theme catalog surface: thesettings-themes-catalog-{list,status,sandbox-banner}DOM wiring,themesCatalogIsSandbox/themesCatalogPaddleEnvstate, thesetThemesCatalogSandboxBannerreducer (legacysandboxflag → newpaddleEnvfield), thethemes-catalogIPC handler shape (returnssandbox+paddleEnvon success),reconcileThemeLicenses’s paddleEnv capture,fetchWithTimeoutabort semantics,fetchThemeServerJson/fetchThemeServerBufferrouting throughfetchWithTimeout, and the preload bridge exposinglistCatalogoverthemes-catalog. - Extended
tests/test_catalog_server.pynow drives the full end-to-end purchase round-trip (catalog fetch → Paddle redirect →checkout-success?transaction_id=…→packetsnitch://checkout-success?installUuid=…&themeId=…deeplink → license reconcile → theme cache write) plus the production vs. sandbox paddleEnv assertion on every catalog response. - New
tests/kerberos_conv_decoder.test.jsexercises wiring, registry, hints, and AS-REQ/AS-REP round-trips through both the data-tools panel and main-frontend inline paths using hand-built ASN.1 fixtures. - New
tests/dns_conv_decoder.test.js,tests/snmp_conv_decoder.test.js,tests/dhcp_conv_decoder.test.js, andtests/dhcpv6_conv_decoder.test.jsfollow the same pattern astests/kerberos_conv_decoder.test.js: each one imports the newdecode{Dns,Snmp,Dhcp,Dhcpv6}FromBytes+normalizeSmbDecoderBytes+bytesToHexLower+autoDetectProtoFromBytes+getPacketProtocolDecoderHintexports, asserts the dropdown wiring (SUPPORTED_DECODER_PROTOS.has('dns' | 'snmp' | 'dhcp' | 'dhcpv6'),decodeWithSelectedProtocolswitch case coverage), exercises the newPROTOCOL_DECODER_HINTS/PORT_DECODER_HINTSentries (ports 53 / 161 / 162 / 67 / 68 / 546 / 547 and protocol namesdns/snmp/dhcp/dhcpv6), and round-trips hand-built fixtures through the decoder — DNS label-sequence + compression-pointer + RR-type decoding, SNMP BER VarBind walks, DHCP option-stream decode (incl. RFC 2132 vendor-encapsulated options), and DHCPv6 msg-type + TLV option stream + nested IA + status-code sub-options. - New
tests/notes_summary_integration.test.jscovers the inferred/verified routing (isNoteConcrete,createNoteEntrydefault + opt-in,getNotesSummarySectionheading selection, mixed buckets, legacy fallback),getCurrentSummaryReportMarkdownordering, and session save/load round-tripping theconcreteflag. - New
tests/conv_decodes_stream_stack.test.jscovers the new SMB follow-stream wiring, the stream-stack assembly pipeline, and the single-block decode regression. - New
tests/smb_conv_decoder.test.jsexercises the dedicated SMB Conv decoder including header parsing and tree rendering. - New
tests/summary_stats_weaving.test.jsextensions lock down the new “Inferred Data (from Notes)” / “Verified Notes (from Notes)” summary headings. - New
tests/summary_stats_weaving.test.jsaggressive dedupe in summary LLM promptsdescribe block adds five regression cases that pin down the stronger dedupe wording in the three LLM prompts that produce the consolidated Summary tab and the exported report: (1) the distillation prompt labels dedupe as the most important rule and carries theAGGRESSIVE DEDUPEheader; (2) it explicitly forbids reworded restatements of the same fact and instructs the LLM to drop them; (3) it resolves the Capture Statistics / LLM-blurb overlap by keeping the more detailed version in the blurb section and removing the high-level restatement from the Capture Statistics section; (4) the compaction prompt carries its ownDEDUPLICATErule that covers both the previously compacted summary and the new blurbs; and (5) the per-streamwriteSummaryFromLLMprompt carries the closing “Never rephrase a fact that is already in the running summary” instruction so the blurbs that feed compaction are already deduped before they ever reachrunAnalysisCompaction. - New
tests/wifi_keys_legacy_spawn_placement.test.jsis a structural regression test that locks in the fix above: it asserts the wifi-keys file is staged intestcaseOutputDir(not insidejobOutputDir), is not written at the old buggy location, is cleaned up after the backend closes, and the--wifi-keys-fileflag is still wired into the legacy spawn argv. - New
tests/wifi_keys_rerun_path_mode.test.jslocks down the renderer-sidewifiKeysRerunInFlightflag, the auto-rerun path aftersetBackendWifiKeyssucceeds, thewifi-keys-<jobId>.jsonfilename pattern, and the keystore round-trip (session save → restore → re-send keys → backend decrypts). - New
tests/epmap_conv_decoder.test.js,tests/llmnr_conv_decoder.test.js,tests/nbns_conv_decoder.test.js,tests/nbdgm_conv_decoder.test.js, andtests/ldap_conv_decoder.test.jsfollow the same pattern astests/kerberos_conv_decoder.test.js: each one imports the newdecode{Epmap,Llmnr,Nbns,Nbdgm,Ldap}FromBytesexports, asserts the dropdown wiring (SUPPORTED_DECODER_PROTOS.has(...),decodeWithSelectedProtocolswitch case coverage), exercises the newPROTOCOL_DECODER_HINTS/PORT_DECODER_HINTSentries (ports 135 / 5355 / 137 / 138 / 389 / 636 and protocol namesepmap/llmnr/nbns/nbdgm/ldap), and round-trips hand-built fixtures through the decoder — EPM tower decoding, LLMNR RR walks, NBNS 32-byte NetBIOS name decoding, NBDGM header walks, and LDAP filter + attribute tree rendering. - New
tests/stats_anomalies.test.jsexercises every detector in the new Stats → Anomalies sub-tab:detectStatsAnomaliesPortscan(per-source port sweep detection),detectStatsAnomaliesBruteForce(per-service / per-destination brute-force windows over FTP/SSH/Telnet/SMTP/POP3/IMAP/RDP/VNC/LDAP),detectStatsAnomaliesBaselineOutliers(per-minute packet-count / length baseline drift),detectStatsAnomaliesEmbeddedContent(high-entropy cleartext payloads), thecollectStatsAnomaliesaggregator, and therenderStatsAnomaliesPanelrendering pipeline (click-to-filter cards). Includes fixtures derived from the newsamples/pcaps/portscan.pcapandsamples/pcaps/ftp-crack.pcap. - New
tests/threat_intel_scorer.test.jsexercises every public helper in src/ui/panels/threat-intel-scorer.js:collectIpIndicators/collectDomainIndicators/collectHashIndicators/collectReputationIndicators,calculateShannonEntropy,detectProtocolAnomalies,clampScore,computeSessionThreatScore(incl. band classification, weight breakdown ordering, capture footprint rollup),buildSessionThreatScoreBreakdown(Markdown + JSON shapes), andbuildSessionThreatLlmPrompt(the prompt sent to the active LLM for assessment). Fixtures include malicious hash hits, Tor exit-node indicators, high-entropy cleartext payloads, and intentionally-empty captures. - New
tests/threat_intel_stats_anomalies.test.jscovers the cross-tab wiring: the same indicators surfaced in the Stats → Anomalies sub-tab also lift the Session Threat Score, and recompute-after-lookup keeps the score in sync as the analyst adds threat-intel lookups. - New
tests/legacy_all_host_sentinel.test.jsasserts thelegacyAllHostSentinelvalue survives progressive snapshot / rerun rewrites of the host list so the0.0.0.0“All Hosts” target keeps binding to the full packet set. - New
tests/test_backend_wireless_80211_wep.pyexercises the WEP decryption path end-to-end:getWirelessLayersrecognisesDot11WEPas the encrypted layer,_wepDecryptround-trips thewep-A4-81-53-B4-CF.pcapsample (WEP-40 keyA4:81:53:B4:CF, BSSIDc0:4a:00:80:76:e4) into bytes that pass the LLC/SNAP + valid-EtherType sanity check,_wepPlaintextLooksValidrejects a wrong key (no false positives) and arbitrary garbage, anddecryptWifiPayloadreturns the same{ok, plaintextHex, algorithm, bssid}shape for WEP that the AES-CCMP path already returned for WPA2. Also covers_resolveWepKeyaccepting WEP-40 / WEP-104 / WEP-128 hex keys and rejecting malformed lengths. - New
tests/list_panel_app_protocol.test.jsis a structural regression for theWIFI-relabelling bug: it loads the realsrc/ui/panels/list-panel.jssource viavm.runInContext, extractscollectDecodedProtocolNames(and the helpersisLinkLayerProtocolName/LINK_LAYER_PROTOCOL_NAMES), and pins down both halves of the fix — a decrypted TCP frame whosedecoded_protocolsstill contains a legacyWIFItoken must not be surfaced asWIFIin the App Protocol column, and a fresh backend payload whosedecoded_protocolsis{WIFI, TCP, HTTP}must reportTCP/HTTP(and neverWIFI) as the application-layer label.
✨ Features
- Extraction panel now handles archives & compressed bundles — the extractor (
src/backend/extractor.py+ the new helpers undersrc/ui/panels/extraction-panel.js) now natively unpackscab,7zip,zip,tar,gz,bzip2, andzstdarchives and compression, so an analyst can drop a captured package bundle onto the panel and the contents surface under their own sub-entries instead of being dumped as a single opaque blob. Archive / compression detection runs on file magic first and falls back to extension matching, and the panel keeps the per-entry carve context menu (load into Extraction / Decoders, send to VirusTotal) consistent across nested entries. - Image, executable, and hash extraction complete the framework — the extraction panel now covers the rest of the practical artifact classes: PNG / JPEG / GIF / WEBP image bytes are auto-classified via
inferMimeTypein src/ui/main-frontend.js, generic PE / ELF executables are detected via the same magic-byte pipeline, and the SMB / NFS / FTP file-carve context menu (carveCurrentStreamToFileFromContextMenu) plus the manual-carve row in the Data Tools panel surface those bytes for save. Loading any extracted artifact — archive entry, carved file, or manual carve — into the Conv input auto-populates the full hash grid in the Hashes sub-tab viacomputeDataToolsHashes: MD5 / SHA-1 / SHA-256 / SHA-384 / SHA-512 + SHA3-256 / SHA3-512, RIPEMD-160, and Whirlpool, with thebuildConvHashesMarkdownTablehelper available for report export. PDFs already detected; Office documents, scripts, and the unified “extract all” bulk-export workflow remain TODO. - Statically linked backend for hostile / unknown environments — the snitch Python backend is now repackaged through
staticxso the shipped binary no longer needs a co-located Python or system libraries to launch. The post-pyinstaller pipeline (scripts/build-backend.js+ the newscripts/with-libs-path.jsenv wrapper) runs thesnitchbinary with the host’s dynamic loader path so the in-test backend boots cleanly on Kali, fresh containers, and minimal CI images, and the standalonesnitch-extractbinary gets the same treatment viasrc/backend/requirements-extract.txt. Thetests/test_backend_{compile,json,server}.pysuites now run through the staticx-wrapped binary by default andconftest.pyis the single source of truth for staging the build. - “Load full packet (with headers) into Conv” via right-click context menu — every packet now has a new
Load full packet (with headers) into Convaction on the right-click context menu. The selected packet (link-layer headers and all) is handed to the Conv tab as the analysis input so the analyst can immediately walk the L2/L3/L4 structure in the data-tools panel without a separate “open in Wireshark” hop. - Backend build cache + Windows installer hardening —
scripts/build-cache.jswraps the PyInstaller + staticx + snitch-extract pipeline in a content-hashed cache so unchanged rebuilds skip the slow steps (PyInstaller already only re-runs when the source fingerprint changes), and the same wrapper produces a single canonicalsnitch+snitch-extractpair for packaging. The Windows installer scripts (src/main.jsSquirrel hooks +tests/windows_elevation_check.test.js) now create both the Start-menu shortcut (location corrected to land underPrograms\PacketSnitchconsistently) and the desktop icon from the same PowerShell helper, so per-machine vs. per-user installs can no longer disagree on where the shortcut lands. The Start-menu and desktop icon flow is fully test-covered and idempotent — a re-install no longer produces duplicate shortcuts. - Self-hostable backend env on stripped-down hosts —
scripts/with-libs-path.jsis the single env wrapper that augmentsLD_LIBRARY_PATH/PATH(and the Windows equivalents) before launching the snitch binary, andsrc/back-comm.jscalls into it for the newtestcaseOutputDir-based keys-file staging. The result: a freshly built snitch binary boots on a vanilla container where the only Python on disk is whatever the staticx build embedded. - Metrics install UUID is now minted exactly once — the install-UUID minting path that powers
metricsInstallIdno longer regenerates the UUID on every settings-save or consent decision, so the install identity stays stable across upgrades, settings changes, and a “restore defaults” pass. The fix is wired intosrc/metrics.jsand pinned by the newtests/metrics_privacy.test.jsextension that asserts the UUID survives a clean save → load → save round-trip. -
Default Ollama model updated to
minimax-m3:cloud— the bundled default model name in the LLM dropdown now matches the current model line, so a clean install no longer requests a model the user doesn’t have and silently falls back to no-LLM. - New
tests/windows_elevation_check.test.jsextension covers the Start-menu and desktop-icon creation helpers (idempotent across re-installs, correct location, correct per-machine vs. per-user branch). - New
tests/test_backend_{compile,json,server}.pyextensions drive the staticx-wrappedsnitchbinary end-to-end so the build-cache wrapper and thewith-libs-path.jsenv shim are exercised on every CI run.
v2.4.2169 - 2026-07-27
Type: minor
✨ Features
- Halfed exe/rpm/deb installer sizes
- Anonymous, opt-in usage metrics: new
src/metrics.jsmodule +metricsapiIPC bridge ship tab/subtab usage, action timing, and error reports to a user-configurable HTTP endpoint (defaulthttp://64.227.4.43:8088/mhook) - First-run consent overlay: clean installs now show a dedicated Yes/No dialog before any metrics can be collected; decision is recorded via
settings.privacy.metricsConsentAskedand can be changed any time in Settings → Privacy - New
Privacysubtab under Settings: enable/disable metrics, set the endpoint URL, tune flush interval and in-memory queue size, and view the auto-generated install UUID - Self-hostable metrics endpoint (
src/metrics/server.py) with NDJSON-on-disk sink for log aggregators (ElasticSearch/Graylog/Loki/Splunk/Vector) plus a/healthzliveness probe - “Analyze IP…” submenu in the context menu: route the right-clicked IP straight into the Subnet Calculator or the Threat Intel lookup
- Carved-file context-menu entries in Stats: “Load carved file into Extraction”, “Load carved file into Decoders” (with auto-extension hinting:
.jpg→jpeg,.json→json,.xml→xml,.html→html,.yaml/.yml→yaml, etc.) and “Send carved file to VirusTotal” for entries in the new Carvable Files list runThreatIntelIpLookup/setAnalysisInputexposed on the Subnet Calculator panel for cross-panel IP drill-down- Backend port reclaim: on GUI startup we look for a stale snitch HTTP backend on the configured port and shut it down gracefully (with OS-level kill fallback) before launching our own
🐛 Fixes
- Heatmap map projection calibration is now sourced from a single
MAP_PROJECTION_CALIBRATIONconstant insrc/settings.js; the previously hard-coded values instats-panel.js(and persisted debug settings defaults) now agree - VirusTotal startup diagnostic is coalesced: eager + persisted-settings + backend-ready callers share a single in-flight probe with a 30s “last successful fetch” dedupe window, and a new
invalidateVirusTotalDiagnosticsCache()hook refreshes immediately when the API key changes - Privacy block in
settings-updateIPC is now deep-merged so a partial write (e.g. the metrics service writing back a fresh install UUID) can no longer wipe the user’smetricsEnabledtoggle or custom endpoint URL - Metrics flush loop now actually fires:
mainWindow.webContents.send("metrics:flush-request", ...)is paired with a renderermetricsapi.onFlushRequestlistener and abeforeunloadfinal-flush, so events ship instead of piling up in the queue install-screenonly auto-shows the consent overlay on a clean install (not on every launch where the install screen is suppressed) and records the choice before un-hiding- Resolved a stale
<<<<<<< HEADmerge marker in the Conv Decodes protocol dropdown (src/index.html) - Context menu layout: “Open in Heatmap” and the new carvable/extraction entries are no longer orphaned by misplaced
<hr>dividers
🗑️ Removed
- Duplicated
0.55 / 0.95 / -0.53 / 0heatmap calibration constants insrc/settings.jsandsrc/ui/panels/stats-panel.js(consolidated intoMAP_PROJECTION_CALIBRATION)
🔧 Improvements
- New
tests/consent_overlay.test.js,tests/metrics_privacy.test.js,tests/metrics_tab_tracking.test.js, andtests/test_metrics_server.pycover the new consent overlay, privacy settings, tab-tracking path, and the Python metrics server end-to-end - New
tests/heatmap_projection_calibration.test.jslocks the shared calibration constants so future tweaks do not silently regress the heatmap alignment - Settings schema expanded with a normalized
privacyblock (metricsEnabled,metricsConsentAsked,metricsEndpointUrl,metricsFlushIntervalSeconds,metricsMaxQueueSize,metricsInstallId) src/ui/main-frontend.jsnow listens for thepacketsnitch:settings-updatedCustomEvent so out-of-band writes (consent overlay, metrics install-id generation) re-sync the in-memory settings and the Privacy tab form- All metrics tracking respects a strict
SAFE_PROP_KEYSallowlist and per-key length caps — no PCAP paths, IPs, prompts, or other user content ever leave the renderer - Docs (
docs/context-menu.md) updated to describe the new Analyze IP submenu and the carvable-file context actions - Default Ollama model updated to
minimax-m3:cloudto match the current model line - Metrics endpoint URL is user-configurable; the bundled
src/metrics/server.pyis documented and ready to deploy behind any reverse proxy for self-hosted setups
v2.4.2115 - 2026-07-26
Type: minor
✨ Features
- Threat Intel sub-tab under Conv (VirusTotal lookup, hash cross-reference button, auto-select in VirusTotal)
- Subnet calculator panel in Conv (queries backend HTTP server for GeoIP and IP reputation data)
- Nmap scan support and IP/Subnet analyzer rearrangements
- Shodan support in IP information
- High-resolution raster worldmap image for Heatmap view
- More sample pcaps
- Better plugin capabilities enforcement
- Sample plugin
hello-snitch.zip - Backend ico of the snitch
- Distiller for the summary report generator
- Bugfixer added to website
- More info for LLM
🐛 Fixes
- File importing no longer errors on successful load, now also shows in Stats panel
- Stats tab Total Traffic indicator
- Path validation fix in
snitch.py(security fix for path traversal) - HTML/content layout shift on docs site
- Conv tab hex input scrolling
- Filter bleed bug
- Filter autocomplete
🗑️ Removed
- Lots of old dead code removed during major code cleanup
🔧 Improvements
- Major code cleanup; lots of old dead code removed, function organization improved
- Streamlined the frontend with stream payload caches
- Frontend processing optimizations
- Fixed HTTP body and file carving
- Subnet Calc subtab UI cleanups
- Heatmap zoom support
- Backend scheduler optimizations
- Better stream hydration/dehydration logic
- Code organization: parted out backend’s individual protocol decoders to their own files
- Code organization: moved sidebar protocol renderers to
src/ui/decoders/main/ - More backend test coverage
- Better font shipping/fallbacks for docs site
- Markdown supported in Summary tab
- Better filter bar logic with saving/labeling filters
- Search and replace in Conv tab
- File Carving section added to Stats tab
- Better Conv to Notes
- Conv tab “load more” button for large data
- Setting to disable Summary auto-generation
v2.3.1694 - 2026-07-23
Type: patch
🗑️ Removed
patchallfrommake(under Windows)
🐛 Fixes
- Removed
patchallfrommakefor Windows so it completes under Windows - Quieted noisy backend HTTP error on stop
🔧 Improvements
- Took out
--iconfrom PyInstaller and added to.specfor backend icon - Backend ICO of the snitch
v2.2.1638 - 2026-07-18
Type: minor
✨ Features
- IPv6 support improvements (brackets on port usage, fixed delimiter issues)
- Threat Intel sub-tab under Conv (VirusTotal)
- Backend HTTP server (refined)
- More sample pcaps
- Table of contents in docs
- Better stream hydration/dehydration logic
- Filter autocomplete
🐛 Fixes
- List panel blank bug
- Big-endian nanosecond resolution pcap support
ports.jsonremoval from samples (no longer needed)
🗑️ Removed
ports.jsonfrom samples (no longer needed)- Screenshots from frontend docs
🔧 Improvements
- Removed screenshots from frontend docs
- Backend pushes packets in chunks for more responsive frontend
- Lazyload packets; call everything from drive instead of loading entire capture into memory
- More decoders added
- Keystore autoadd objects added
- Backend existence check before run
- Filter query status updates
- Explicit logging of filter clear
- Stream statistics in Host Data view and Stats tab
- Conv tab data saved with session data
- Context menu reference in docs
- Differentiation between
tcp.protoandudp.proto, newapp.protocatchall - Locations listing queryable on click via
loc.src.city || loc.dst.city - Globbing-style regex support in filter (now supports
?and*) - Brotli detection
- Help button + new window to docs
v2.1.1606 - 2026-07-16
Type: minor
✨ Features
- Soulseek, Bittorrent, and SMPP support
- Better PPP, PPPoE, and LLDP support
- Improved plugin capabilities with new internal interaction APIs
- Better crypt support for ambiguous data
- Improved XML, JSON, YAML support
- Generic decoders renamed
- Updated build config
- Backend now fails on unknown filetype magic
- GitHub workflow fixes (repo stats)
🐛 Fixes
- List panel blank bug
- Big-endian nanosecond resolution pcaps
🗑️ Removed
- Non-clickable password badge from UI
🔧 Improvements
- Plugin enforcement tests
- Widened settings panels
- Improved plugin UI handler
- Some plugin support groundwork
- Parted out backend protocol decoders to their own files for better organization
- Widened sidebars for both OSs
- Backend chunk size now arbitrary
- Streamlined frontend with stream payload caches
- Fixed creds uniqing bug in Stats panel
- Fixed HTTP body and file carving
- Cleaned up UI, removed non-clickable password badge, fixed subnet calc title
v2.0.1573 - 2026-07-15
Type: major
⚠️ Breaking Changes
- Version 2.0 represents a major version bump with substantial architecture changes
- New session save file format (gzipped BSON) replaces the previous format; old session saves are not compatible
✨ Features
.psbis on by default in debug mode- New session save file format based on gzipped BSON (binary JSON)
- Multi-job processing on backend and frontend with job IDs
- Backend’s common/ library fixed
- Conv tab retooled for better use of screen real estate
- List tab columns hidden by default
- LDAP decoder support improved
- Updated Samba code
- Markdown compatibility for LLM and send-to-Notes data shapes
- Startup splash screen
/statusendpoint on backend’s API- Major serialization bug fix on backend
- Streamlined packet ingestion on the frontend
- List tab support for zero-length payloads
- Frontend logging tunables to reduce UI main thread load
- New defaults for UI main thread logging backlog
- List tab column rearranging support
- Settings tab improvements
- Backend version line in packetsnitch exe gen
- Themes bundled
- Updated Python spec file for snitch.py
🐛 Fixes
- SIP handling under Conv tab decoders
- Better pagination for large streams loaded into Conv
- Better formatting in Conv tab
- Filter bleed bug
- Word wrapping touchups
- Settings overlap bug
- List tab scrolling bug on virtualized lists
- Host Data view content hidden bug
- List tab zero payload length bug
- Dropdown colors now follow themes
- Backend bug finding common/
🗑️ Removed
- Unnecessary testcases dir creation in HTTP mode
🔧 Improvements
- Version bump
- Updated python spec file for snitch.py
- Increased fade out sec
- Longer startup time
- One-shot override for the decodes subtab on Conv
- Session picker updates
- Better LDAP decoder support
- Updated Samba code
- Removed unnecessary testcases dir creation in HTTP mode
- Updated User-Agent string globally
- Updated Help tab to allow new domain
- Updated settings tab
- New Release check
- Download new release button
- SnitchBitch theme updated
- Fav icon added
- More backend test coverage
- Better plugin capabilities enforcement
v2.0.1566 - 2026-07-14
Type: minor
✨ Features
.psbis on by default in debug mode- Backend’s
common/library bug fix - Multi-job processing on backend and frontend with job IDs
- New session save file format (gzipped bson)
- Major serialization bug fix on backend
- SIP handling improvements in Conv tab
- LDAP decoder improvements
- Updated Samba code
v1.9.1518 - 2026-07-11
Type: minor
🗑️ Removed
- References to
models.txtfrom code - Test automatic run (left tests manual)
- Map crosshair
🐛 Fixes
- Removed references to
models.txtin code, leftmodes.jsonsupport - MAC address pulling IP into field bug on backend
- Backend kickoff improvements
- Map updated, crosshair removed
- Map animations smoother
🔧 Improvements
- Updated map animations for smoother zoom
- Streamlined the frontend
- Stream payload caches
- Frontend processing optimizations
- Fixed creds uniqing in Stats panel
- Backend chunk size now arbitrary
- Widened sidebars for both OSs
- Removed test automatic run, left tests manual
- Updated SnitchBitch theme
- Favicon added
- Updated Sponsorship stuff
- Thanks to IRC network admins where dev chan is hosted
- New Release check added
- Download new release button added
- Pulling data from runtime main process
- Updated download link generation handler
- Now detect Windows/RedHat/Debian flavors
- Better Nmap scan support
- Shodan support in IP/Subnet analyzer
- Threat Intelligence under IP information in Subnet Calc subtab
- Better documentation
v1.9.1442 - 2026-07-05
Type: minor
✨ Features
- Worldmap
- Backend HTTP server
- PGP workspace in the Crypt tab
- Settings tab
- LLM now in frontend
- Heatmap with Wikipedia images
- Layer fix; statusbar brought forward
- Better default zoom/offset options on map calibration
- Heatmap zoom support
- Heatmap, “Worldmap” view
- Subtab for Threat Intelligence under IP info
- Credential count and creds readout in Stats tab
- Rudimentary protocol support for SIGTRAN packets for SS7 network captures
- Anime theme (animated background, demo purposes)
- New light soft pastels theme
- Better theme engine (backdrop images support)
- PGP tab (basic implementation; decrypt and verify messages)
- Backend API over JSON HTTP
- Better Filter history (filter history saves/labeled)
- Better filter bar logic
🐛 Fixes
- Removed duplicates in the backend JSON
- Preprocessor’s legacy keys cleaned up; removed all non dot-notation keys
- This can be a breaking change on session saves; you may need to remove old saves or use them with the previous version of PacketSnitch
- Conv tab hex input scrolling
- Filter validation strips
!,(,) - Initial check on Group by Stream in List tab
- Some small aesthetic changes
- Removed need for
threadsvar in backend config - Backend now pulls the number of available CPU cores dynamically
⚠️ Breaking Changes
- Saved sessions need to be discarded and rebuilt from their pcap
- Old session saves may not work due to legacy key removal
🗑️ Removed
- All non dot-notation (legacy) keys from the preprocessor output
- Need for
threadsvar in backend config (now pulled dynamically from available CPU cores) - Duplicates in the backend JSON
🔧 Improvements
- New themes: pastels and Sub7
- Tweaked the light theme
- New ico location
- Encodings as a hidden import for the python backend .deb builds
- Forge config converted to conditional builds based on building OS
- Windows installer icons fixed
- Added things to make deb build correctly under Kali
- Template for .desktop so the icon works properly under Linux
- UPX packing option (not default)
- Installer splash
- Backend scheduling optimizations
- Resize and show/hide columns in List tab
- Backend version string; version can be queried via /version
- Version check for the packetsnitch backend api
- PGP autoloads its secrets into the keystore
- Heatmap scales better
- Backend now has a proper version string
- Hardened FRAME and unknown behavior fallback
- Normalized key format
- Added src/dst port aliases
- Retooled the map zoom algorithm
- Map calibration edits
- Backend scheduler optimizations
- Repositioned Prev/Next
- LLM diagnostics in activity log
- Backend now works correctly with the server
v1.8.1384 - 2026-07-01
Type: patch
🗑️ Removed
- Unneeded reqs in
requirements.txt - Ollama dep from backend (LLM moved to frontend)
- LLM references and calls from backend
_dirnameand_filenamepatches (replaced with DefinePlugin and fallback)
🐛 Fixes
- Removed unneeded reqs in
requirements.txt; let pip handle that - Windows cPyInstaller should be able to use Linux forward slashes in spec file for snitch.py
- Pushing startup hardening to the backend loader
- Pushed
_dirnameand_filenamepatches - DefinePlugin and a fallback for dirname
- Tried to fix Windows no style/js runtime bug
- Tried to fix ollama compile issue
🔧 Improvements
- Ver bump and removed ollama dep in backend
- Does a check to see if LLM Ollama backend is running before exposing it in UI
- Updated LLM with a longer default timeout for slow models
- LLM calls are now handled on a per-stream basis in the frontend
- LLM references and calls removed from backend for performance and stability
- PacketSnitch now scales better for larger capture files
- Notes markdown dep “marked” added so tables are properly generated
- Markdown and note edit mode now works properly
- Setting for the stream size warning threshold
- New Settings tab
- Theme engine: select quit button icon and color
- Two new themes: pastels and Sub7
- Theme engine: light theme tweaks
- Fixed pretty JSON screen in Conv
- Updated
snitch.pyto take a chunk size var from frontend’s user config - Credentials counter in Stats section
- Some context-aware LLM stuff
- More stream/packet targeted ollama support on frontend
- Improved filter grepping to include label/content/type hint of keystore entries
- Fixed keystore filtering; only visible when session keychain is in use
- User can filter keystore list if many entries
- Touchups to logging code
- Theme engine: old theme carryover fixed; resets all themable colors on new theme selection
- Updated docs and themes guide
- Theme engine: support for logo replacement
- Some logging and status updates on settings changes
- Settings logging to activity log on changes
- Theme selection decision matrix
v1.8.1332 - 2026-06-28
Type: minor
🐛 Fixes
- Try checking the type and returning if not an arr
- Fixed for zero packets warning
- Correction so it doesn’t return before clearing the old packet data
- Added check to see if a key is valid if zero packets are returned by the filter
- Added link to license
🔧 Improvements
- Initial test of LLM in frontend
- Per-packet/key filtering and grouping
- Stats tab Total Traffic indicator fixed
- Began adding heatmap functionality
v1.7.969 - 2026-06-24
Type: minor
✨ Features
- Globbing-style regex support in filter (now supports
?and*) - Brotli detection
- Help button tied to new window that loads the documentation from the internet
- Access controls to help page browser
- Extensive features list section in documentation
- Two more sample captures
- New compression sample
- New filter keys added to docs
🐛 Fixes
- Backend clobbering bug when a new session was loaded before the backend was done
- Some corrections in backend to what layer a proto returns as
- Sets user agent to PacketSnitch and version for web reqs
- Made demo gif a link to YouTube vid of better resolution
- Some more guards on the URL the Help center can browse to
- Some guards on hideAllData() so hex grid no longer has lingering null
- Now hides data types list on BGP packets too by default
- Repositioned the Overview in the Stats tab form one to three columns
🔧 Improvements
- Added
link.protocontext menu filter options - Relabeled transport and application filters in context menu
- Added some proper protocol keys to backend for more descriptive filtering (e.g.,
transport.proto: tcp, orlink.proto: ethernet) - Added some new examples for globbing feature
v1.7.935 - 2026-06-21
Type: minor
✨ Features
- Decoders: SIP and FTP (with keychain credential autopopulation)
- New filter keys:
tcp.retransmission, retransmission/out-of-order packet keys - Statistics to Stats tab for retransmission
- Data type guesses for network endpoints and MAC addresses
- Date and time format type identifiers
- Single byte data type guessing
- Save Raw Conv tab data via context menu
- Streamlined protocol matching logic
- HTTP following: whole body gets copied to Conv tab and exported to files (not just initial packet)
- Decompression-aware context menu items for handling HTTP data
- Some specific sample data for demos
- Preliminary IGMP backend support
- Properly handles non-TCP formed packets (ARP, FRAME, etc.)
- Some lower-level protocol identifiers for the Stats tab
- Improved protocols used readout in Host Data view
- Some protocol auth extractors
- Filter for bookmarked hosts
- Warning if the stream they try to load is large
- Keystore autoadd to work with packet lazyload on metadata
- Table coloring
🐛 Fixes
- Data types frame auto hides if it doesn’t make sense to have it there
- Renamed exploit pcap
🔧 Improvements
- Better and more targeted context menu
- Uppercased application layer protocols in Stats tab for congruence
v1.7.848 - 2026-06-17
Type: minor
✨ Features
- Backend now pushes packets into frontend in chunks for more responsive frontend
- Code can lazyload packets and call everything from drive instead of loading entire capture into memory
- Increased the number of seconds status update stays on to 10
- JSON/pcap/pcapng constraint on session picker’s new session dialog
- Added support for some more decoders
- Some keystore autoadd objects
- Added check to see if the selected file is a session or pcap; code paths for each
- Backend existence check before trying to run
- Filter query status updates
- Explicit logging of filter clear
- Updated some stream stats on the left hand side for each packet
- Stream statistics in Host Data view and Stats tab
- Conv tab data saved with session data
- Context menu reference in docs
- Differentiation between
tcp.protoandudp.proto, newapp.protocatchall - Locations listing queryable on click via
loc.src.city || loc.dst.city - Removed stray IP addresses from
dns.qnamein Stats page
🗑️ Removed
- Stray IP addresses from
dns.qnamein Stats page - Divider above Exports submenu
🐛 Fixes
- Backend handles packet array sync up properly with guards
- Now only runs autosave if a real session is opened, discards a dummy session (<5000 bytes)
- Autosave function triggers on exit and on log write
- Added check to see if a session is even open before asking to save it on quit
- New session button properly clears out the old session before reloading
- Now from Stats tab, if a Ports Seen port is clicked, it searches for both TCP and UDP ports
back-comm.jsloader handles loading new compressed format session files- Now while data is being pushed from backend incrementally, the tab doesn’t keep jumping back to Host Data view
- The same message about switching to List view doesn’t come up in the log repeatedly on startup
- [Snitch] entries in log no longer have unary space near beginning
🔧 Improvements
- Moved save button to the very bottom
- Removed divider above Exports submenu
- Edited some context menu items to shorten them
- Some changes to the way protocols from application and transport layer protos are added to the filter
- Added gif demo of packetsnitch
- Added a static width to the gif
- Renamed workflow and schedule daily runs
- Added repo stats GitHub action
- Quits without removing the darkening from session picker, for aesthetics
- Recall the
persistSessionToDiskfunction with null session name if autosave session is being manually saved - Fixed autosave behavior
- Exit button on session picker screen just exits picker, not packetsnitch
- Some guards so user can’t save/export the session or reprocess the pcap while being preprocessed
- Fixed session restoring not reloading packet payload data
- Log now notes which Renderer thread a log update is coming from
- Error message corrected for reprocess button
- Catch if reprocess button is hit too soon
- Added some screenshots to the documentation on the frontend
- Made correction to docs
- Stream order and filter context in Host Data tab
- Auto host targeted filtering from the Stats tab
- Session manager now saves the pcap itself to the session file, allows for reprocessing from session file
v1.6.807 - 2026-06-14
Type: minor
✨ Features
- Autosave function that triggers on exit and on log write
- New session file format (compressed)
🗑️ Removed
- Dependency on manually saving session on exit (now handled by autosave)
🐛 Fixes
- Now only runs autosave if a real session is opened
- Discards a dummy session (any session less than 5000 bytes)
- Added check to see if a session is even open before asking to save it on quit
- Fixed new session button so it now properly clears out the old session before reloading
- Loading screen dims PS behind it while active
- Undims when processing is complete
- Added blank hosts list on list panel if only packet is by host 0.0.0.0
- Packet array now syncs up properly with guards
v1.5.731 - 2026-06-07
Type: minor
✨ Features
- Encodings as a hidden import for the python backend .deb builds
- Swapped snitch binary location due to earlier breaking change in forge config
- Conditional forge config builds based on building OS
- .desktop template so icon works properly under Linux
- UPX packing option (not default)
- Installer splash
🐛 Fixes
- Windows installer icons fixed
- Stripped some unnecessary stuff out of backend package
- Added things to make deb build correctly under Kali
- Redid way data is imported into .desktop metadata file, dynamically generated
- Updated that compression block with a notice
🔧 Improvements
- Updated metadata for the builds
- Updated description and uninstaller ico
- Added installer loop gif with installer written on it
- Updated some docs
- New ico location
- Added documentation ai redo
- Some keywords and the terminal explicit false to the .desktop metadata
🗑️ Removed
- Unnecessary stuff from backend package
v1.5.705 - 2026-06-03
Type: minor
✨ Features
- New width/height config so it better fits smaller screens
- Removed need for
threadsvar in backend config - Now pulls the number of available CPU cores dynamically
- Adjusted colors in the Conv color coding for better visibility
- Conv input history UI
- Conv output visibility mapping made explicit
- Hide redundant conv output pane
- Clamp conversion panel overflow
- Constrain conversion panel columns
- Fix Conv output expansion toggle and align output color coding
- Fixed background color in color coded conv boxes
- Prevent drag-and-drop text editing in Conv tab input only
- Prevent drag-and-drop text editing in filter/host inputs
- Fix Conv hex output mouse-drag behavior and dark blue frame backgrounds
- Refine Conv highlight map fallbacks
- Harden Conv selection sync handling
- Add Conv hex color coding and cross-frame selection sync
- Guard conv output expand handlers against duplicate binding
- Add conv output expand and collapse interaction
- Conv text type heuristics
- Refine Conv guess scan behavior and context-menu derive flow
- Add chunked Conv guess scanning and context derive action
- Sanitize host target filter query terms
- Refactor guessDataType: extract UUID/JWT regex constants, name base64 score constants
- Sync target host selection with filter bar
- Fix guessDataType: tighten JWT regex
- Add data type guessing (hashes, base64, PGP, JWT, UUID) below MIME type in Conv tab
- Restrict keystore reset action to unlock mode
- Guard nested keystore reset action
- Polish keystore dialog wipe button
- Refine keystore dialog reset flow
- Add keychain wipe action to unlock dialog
- Log renderer console.error to activity log
- Tidy keystore password confirm check
- Fix keystore reset function scope
- Centralize keystore reset warning text
- Centralize keystore minimum password length constant
- Add keystore password reset with wipe warning
- Keep loading screen visible until packets render
- Modified Python requirements.txt setup slightly so it doesn’t bork on linux
🗑️ Removed
- Need for
threadsvar in backend config (now pulled from available CPU cores dynamically)
🔧 Improvements
- Normalizing and removing some duplicated things
- Got most of the logging stuff congruent
- Add build from source instructions to README
- Added proper patching for the node_modules rpm builder bug
- Updated some logging code
- Added the frontend to the installed screen
- Fixed path for installed files locator
- Removed compiled backend dir, added tagline logo
- Updated ps views gif
- Fixed paths for installer screen checks
- Removed the patchup build from regular npm run make so it completes under Windows
- Updated version
v1.5.618 - 2026-05-30
Type: patch
✨ Features
- Added the frontend to the installed screen
- Fixed path for installed files locator
v1.5.610 - 2026-05-29
Type: minor
🗑️ Removed
- Compiled backend dir
- Patchup build from regular
npm run make(so it completes under Windows)
🔧 Improvements
- Updated
ps_views.gif - Removed compiled backend dir, added tagline logo
- Added tagline purple
- Fixed paths for installer screen checks
- Removed the patchup build from regular
npm run makeso it completes under Windows - Updated README
v1.4.508 - 2026-05-26
Type: minor
✨ Features
- Stream filter to when selecting packet from List or Stats screens
- Hash algorithm outputs (MD5, SHA-1, SHA-256/384/512, SHA3-256/512, RIPEMD-160, Whirlpool) to Conv tab
@noble/hashesandwhirlpool-jsfor Conv tab hash outputs- Refactor: switch Conv hash functions to pure JS crypto implementations
- Reorganize context menu copy and export cookie actions
- Improved auto cookie entry labels and naming clarity
- HTTP File context menu branch for dumping HTTP uploads/downloads
- Auto-add HTTP cookie jar entries to session keystore
- Cookie jar context menu branch
- Add to Keystore context menu for highlighted text
- Context-menu action to load raw payload into Conv tab
- Context-menu action to load the full raw packet (entire frame) into Conv tab
- Multi-level submenu viewport positioning via ancestor panel revelation
- Extract cookies and generic POST body credentials into session keystore
- Auto-detect credentials in HTTP, SMTP, POP3, IMAP, Telnet and add to session keystore
- Session key bookmarking to persistent keychain
- Password-gated keychain manager with session/persistent modes
- First-time keychain password setup flow
- Persist keystore in IndexedDB with encrypted entry payloads
- Split keystore panel and harden keystore/filter handling
- Move keystore UI to standalone panel and add Keystore menu tab
- Tighten Telnet auto-detection to require IAC + valid command byte
- Add HTTP, Telnet, SSH, POP3, IMAP, SMTP decoders to Conv tab
- Implement Crypt tab with SSL workspace and persistent local keystore
- Filter syntax validation and error reporting
- Context-menu actions for explicit filter parentheses
- Avoid double wrapping negated context filter clauses
- Add unary
!filter inversion and context-menu is-not actions - Add explicit
&&and||filter-append context options - Add Clear and… submenu under Add to filter context menu
- Remove double
[Console]label on renderer console.log entries - Label all backend process logs as
[Console][Backend]in activity log - Label backend errors as
[Console][Backend]in activity log - Prefix all UI log entries with
[GUI][UI] - Log console output to activity log
- Adjust tab bar width and Prev/Next placement
- Move Prev/Next buttons to far right of tab bar
- Swap Stats/Data tab positions and rename Data tab to Conv
- Tighten context menu active cursor guards
- Normalize context menu active packet cursor handling
- Sync active packet cursor for context menu helpers
- Use active packet accessor in context menu export
- Flip context submenus inside viewport
- Restructure context menu into convert/filter/export branches
- TLSv1.2 context to satisfy backend security scan
- Allow TLS protocol negotiation with min TLS1.2
- Improve TLS probe reliability with SNI fallback
- Use explicit TLSv1.2 context for banner SSL probe
- Refine HTTPS service checks and SNI hostname selection
- Harden TLS context and tidy HTTPS service detection
- Fix TLS encryption details for HTTPS-like services
- Replace filter history popup with standard select dropdown
- Add tooltip to Conv tab button
- Hex-view copy actions to right-click context menu
- Unify right-click context menus and conditional copy visibility
- Right-click context menu with copy, paste, and save JSON
- Blank right panel when list or stats tab is active
- Darken selected packet row on hover in list tab
- Use Unknown fallback for stream grouping when protocol is missing
- Sortable packet-list columns via clickable headers
- Enhance list tab with hover highlight, bookmark column, and stream grouping
- Add List tab between Stats and Log for packet browsing by host
- Filter aliases and text matching for
wire.proto,eth.src.vendor,mime.type,dns.qname - Capture stats page with protocols, hosts, locations, and more
- Polish query-highlight loop and dropdown event guards
- Refine query-history empty-state styling and toggle handling
- Improve query syntax UI accessibility and keyboard behavior
- Allow empty query-history dropdown to open with empty-state message
- Add syntax highlighting for filter query input and history dropdown
- Packet query history dropdown for reruns
- Use cached host value in packet display logging
- Normalize error log helpers and host iteration
- Log all remaining frontend error paths
- Harden activity timeframe parsing and normalize log entry format
- Fix IPC placement and harden activity log capture entries
- Add activity log UI and persistence plumbing
- Filter to stream when selecting packet from List or Stats screens
- Refactor: simplify packetsForHost fallback in stream filter path
- Preserve active filter and packet on Host Data reopen
🐛 Fixes
- Fixed Prev Next positioning to something sane
- Some UI tweaks with more error messages
- Fixed tabs opacity
- Resized data tools input box (taller)
- Fixed port and Network Class context menu filter detection
- Adjust port handling in context filter parser
- Tighten context filter query sanitization
- Harden context filter value handling
- Append context-menu filters to existing query
- Fix context filter parsing edge cases
- Tighten editable target checks for context-menu paste
- Avoid deprecated execCommand in context-menu paste flow
- Fix context-menu paste targeting and reorder save option
- Harden save-json handling and rename action visibility flag
- Add success status feedback for context menu copy
- Apply review nit in unified context menu rendering
- Clean up catch blocks and byteIndex naming
- Rename hex grid iterator variable for clarity
- Fix context menu closing and startup hidden state
- Address review nits for catches and docs labels
- Refine context conversion validation and comments
- Fixed some box sizing stuff
- Fixed log search box positioning
- Enforce fixed column widths in packet list table
- Normalize and sanitize stats entries on Stats page
- Simplify hex grid cell wrapping rule
- Keep hex payload grid cells square at all zoom levels
🔧 Improvements
- Edit author line
- Removed eslint backup
- Added chardet to backend’s requirements.txt
- Some minor changes to themes
- Updated documentation for new codebase changes
- Added screenshot of the list screen
v1.3.353 - 2026-05-24
Type: minor
✨ Features
- Updated some electron forge stuff
- Updated python requirements.txt
- Fixed log search box positioning
- List tab between Stats and Log for packet browsing by host
- Enforce fixed column widths in packet list table
- Remove unused
.packet-list-host-headerCSS class - Add screenshot of the stats tab
- Normalize and sanitize stats entries on Stats page
- Filter aliases and text matching for
wire.proto,eth.src.vendor,mime.type,dns.qname - Capture stats page with protocols, hosts, locations, and more
- Polish query-highlight loop and dropdown event guards
- Refine query-history empty-state styling and toggle handling
- Improve query syntax UI accessibility and keyboard behavior
- Allow empty query-history dropdown to open with empty-state message
- Add syntax highlighting for filter query input and history dropdown
- Packet query history dropdown for reruns
- Use cached host value in packet display logging
- Normalize error log helpers and host iteration
- Log all remaining frontend error paths
- Harden activity timeframe parsing and normalize log entry format
- Fix IPC placement and harden activity log capture entries
- Add activity log UI and persistence plumbing
- Simplify hex grid cell wrapping rule
- Keep hex payload grid cells square at all zoom levels
- Added chardet to backend’s requirements.txt
🔧 Improvements
- Removed eslint backup
- Some minor changes to themes
- Update screenshot in README for main view
- Added screenshot 24
🗑️ Removed
eslintbackup
v1.2.310 - 2026-04-23
Type: minor
✨ Features
- Fix frontend to safely read MAC fields when Ethernet Frame may be N/A
- Show MAC address info when one IP is private and other is internet
- Save JSON from in-memory data instead of copying backend temp file
- Update image source in README.md
- Fix off-by-one so first packet in each host list is reachable and bookmarkable
- Sync bookmark dropdown when target host changes
- Extract
syncBookmarkDropdownhelper to remove duplication - Sync bookmark dropdown with current packet navigation
- Add eslint fixes
- Resolve all JavaScript errors in src/ files
- Updated linting config
- Remove
.progress-ps.htmlpage - Load hosts.json directly after backend finishes with 1s delay
- Clarify RADIUS TCP support is per RFC 6614
- Wire new protocol decoders into frontend render pipeline
- Add decoders for FTP, SMTP, POP3, IMAP, Telnet, IRC, MTP, LDAP, MySQL, PostgreSQL, XMPP, SMB, MQTT, RTSP, TFTP, BGP, HTTP/2, NNTP, RADIUS
- Extract protocol decoders into decoders.js
- Wire CopyPlugin to copy
src/assetsinto webpack output so images load - Replace inline base64 PNG images with file references in index.html
- Backend changed to onedir instead of onefile (shaves 4 seconds off backend time, no package unpack)
- Status shows PS version number
- Add
SOFTWARE_VERSIONconst to frontend scripts.js - Add installation complete screen shown on first run after new version install
- Rewrite README.md to focus on frontend and full tool overview
🔧 Improvements
- Hide menu bar
- Fix images so they load from files
- Update reference to Filters documentation
- Update Filters.md with new protocol filter keys
v1.2.254 - 2026-04-16
Type: minor
✨ Features
- Add Filters.md link to main README
- Add comprehensive Filters.md documentation page
- Update README with donation options and installation steps
- Update donation section with QR codes
- Organize docs section
- Update Frontend.md with installation instructions
- Set
numWorkerThreadsto twice the available CPU cores - Make top bar draggable; exclude close button from drag region
- Add aria-label to close button and enable top bar window dragging
- Removed frame
- Revise README with new version and documentation links
- Backed zoom out a bit
- Made error go away automatically on correct filter
- Update README.md
- Reorganize into Backend.md and Frontend.md with all new searchable attributes and output frames
- Add HTTP protocol decoder to backend and frontend
- Changed
packet.protocoltopacket.protofor uniformity - Add SNMP, ICMP, SIP, DHCP, and NTP protocol support
- Add UDP and DNS packet support on backend and frontend
- Default save dialog to user’s Documents folder cross-platform
- Fix pointer to the filesave worker
- Copy temp
hosts.jsonto dest instead of passing data through IPC - Add threaded save-JSON function
- Remove save functions
- Backend exits silently when output dir exists (remove
input()prompt, clean dir before each run) - Reset state on subsequent file loads after saving
- Rename all snake_case function names to camelCase in snitch.py and scripts.js
- Fix code review issues: JSON key literals, string comparisons, comment typos, and filename
- Rename variables to camelCase with descriptive names across frontend and backend
- Now filter with target host selection keeps in track with filter readout
- Add Save JSON button with save-as functionality
- Reloading with optimized backend
- Removed .exe
- Removed useless icons
- Fixed nollm call
- Add LLM enable/disable checkbox to frontend; add
--no-llmbackend flag - Added dev install instructions
- Improve README with images and usage sections
- Updated version
- Fixed part of bookmark code that isn’t available if host pointer is incorrect
- Fixed off by one in bookmark code
- Fixed bookmark code
- Fixed horizontal scroll on packet payload pane (disabled)
- Fix note to remove
dns.hostnames(non-leaf) reference - Add Searchable Attributes section with all dot-notation leaf nodes
- Update README with new version and screenshot
- Added new screenshot
- Updated version
- Fixed timestamp and recon positioning
- Fix payloadascii tooltip not showing on hexg mouseover
- Removed a few old unworking samples
- Added some new properly formatted search parameters
- Added a timer for how long it took to load the file read
- Fixed a few things, added a packet size and filter ret size readout
- Added some new functionality with counting packets on the sidebar
- Fixed some positioning and zindex stuff
- Fixed zoom bug
- Bringing up ver
- Fixed some initial display bugs
- Now properly handles when no packets are returned from filter
- New screenshot, a couple updates to comparison
- Added better filtering mechanism
- Now index should work right (it was adding twice to the array)
- Filter works better now
- Added some filter code
- Filter code is mostly working
- Filtered is almost working right
- Starting to build filter functions
- Better compression logic
- Better DNS host logic, also added catchall error handling
- Better error handling, loading dots blink now
- Made some small changes to the summary box
- Fixed bug if encryption stuff is undefined
- Added an error message function
- Updated some stuff for squirrel install
- Added some failsafes
- Updated screenshot
- Version update
- Fixed a bug in the data box, added a few more data points
- Added a new pane for more extra infos
- Added a nice loading screen
- Cleaned up code a bit, now makes sure backend is dead on quit
- Added icons for the installers and exe
- Building first working bins
- Windows version builds and works!
- Updated for os detection
- Adjustments for build
- Should work better calling the backend now when installed
- Initial commit
🐛 Fixes
- Make all boxes resize relative to window size using flexbox and CSS variables
- Add parenthesis grouping support to packet filter query parser
- Use Unix-style path for non-Windows platforms in back-comm.js
- Resolve all JavaScript errors in src/ files
- Fix payloadascii tooltip not showing on hexg mouseover
- Updated linting config
- Fix some things with eslint
🗑️ Removed
- Window frame (frameless window)
- A few old unworking samples
🔧 Improvements
- Various UI improvements
- Updated version number
- Updated screenshot
- Removed .exe
- Removed useless icons
- Fixed bookmark code
v1.2.227 - 2026-04-15
Type: minor
✨ Features
- Add HTTP protocol decoder to backend and frontend
- Changed
packet.protocoltopacket.protofor uniformity - Add SNMP, ICMP, SIP, DHCP, and NTP protocol support
- Add UDP and DNS packet support on backend and frontend
- Default save dialog to user’s Documents folder cross-platform
- Fix pointer to the filesave worker
- Copy temp
hosts.jsonto dest instead of passing data through IPC - Add threaded save-JSON function
- Rename all snake_case function names to camelCase in snitch.py and scripts.js
- Reset state on subsequent file loads after saving
- Add Save JSON button with save-as functionality
- Add LLM enable/disable checkbox to frontend; add
--no-llmbackend flag - Improve README with images and usage sections
- Updated version
- Fixed part of bookmark code that isn’t available if host pointer is incorrect
- Fixed off by one in bookmark code
- Fixed bookmark code
- Fixed horizontal scroll on packet payload pane (disabled)
- Removed a few old unworking samples
- Added some new properly formatted search parameters
- Added a timer for how long it took to load the file read
- Fixed a few things, added a packet size and filter ret size readout
- Added some new functionality with counting packets on the sidebar
- Fixed some positioning and zindex stuff
- Fixed zoom bug
- Better compression logic
- Better DNS host logic, also added catchall error handling
- Better error handling, loading dots blink now
- Made some small changes to the summary box
- Fixed bug if encryption stuff is undefined
- Added an error message function
- Updated some stuff for squirrel install
- Added some failsafes
- Fixed a bug in the data box, added a few more data points
- Added a new pane for more extra infos
- Added a nice loading screen
- Cleaned up code a bit, now makes sure backend is dead on quit
- Added icons for the installers and exe
- Building first working bins
- Windows version builds and works!
- Updated for os detection
- Adjustments for build
- Should work better calling the backend now when installed
- Initial commit
🐛 Fixes
- Make all boxes resize relative to window size using flexbox and CSS variables
- Add parenthesis grouping support to packet filter query parser
- Various UI fixes
🗑️ Removed
- Window frame (frameless window)
- A few old unworking samples
v1.1.195 - 2026-04-13
Type: minor
🗑️ Removed
- Useless icons
✨ Features
- Removed useless icons
- Added new ver
- Add LLM enable/disable checkbox to frontend; add
--no-llmbackend flag - Fixed nollm call
- Added dev install instructions
- Improve README with images and usage sections
- Fixed part of bookmark code that isn’t available if host pointer is incorrect
- Fixed off by one in bookmark code
- Fixed bookmark code
- Fixed horizontal scroll on packet payload pane (disabled)
🔧 Improvements
- Optimized snitch.py backend - O(1) lookups, GeoIP cache, thread safety, fix bugs
- Backed off threads again
v1.1.184 - 2026-04-12
Type: minor
✨ Features
- Updated version
- Fixed part of bookmark code that isn’t available if host pointer is incorrect
- Fixed off by one in bookmark code
- Fixed bookmark code
- Fixed horizontal scroll on packet payload pane (disabled)
- Add Searchable Attributes section with all dot-notation leaf nodes
- Update README with new version and screenshot
- Added new screenshot
- Updated version #
- Fixed timestamp and recon positioning
- Fix payloadascii tooltip not showing on hexg mouseover
- Removed a few old unworking samples
- Added some new properly formatted search parameters
- Added a timer for how long it took to load the file read
- Fixed a few things, added a packet size and filter ret size readout
- Added some new functionality with counting packets on the sidebar
- Fixed some positioning and zindex stuff
- Fixed zoom bug
- Bringing up ver
- Fixed some initial display bugs
- Now properly handles when no packets are returned from filter
- Added better filtering mechanism
- Better compression logic
- Better DNS host logic
- Better error handling
- Added a nice loading screen
- Cleaned up code a bit
- Initial commit
🗑️ Removed
- A few old unworking samples
🔧 Improvements
- Made all boxes resize relative to window size using flexbox and CSS variables
- Add parenthesis grouping support to packet filter query parser
- Various UI improvements
v1.0.175 - 2026-04-13
Type: patch
🗑️ Removed
- Useless icons
🔧 Improvements
- Removed useless icons
- Version bump
- Updated version
v1.0.158 - 2026-04-01
Type: major
🗑️ Removed
- Window frame (frameless window)
✨ Features
- First stable 1.0 release
- Initial application framework
- Backend Python service integration
- Frontend Electron UI
- Filter system
- Bookmark system
- Hex grid payload viewer
- Info panel and packet details
- Data box with packet information
- Loading screen
- Window frame removed
- Cross-platform installers (Windows, Linux, macOS)
- Auto-update functionality
- Statistics tab
- Stream statistics
- Compression and encoding detection
- Network class detection
- GeoIP location data
- Hostname resolution
- LLM integration in backend
🐛 Fixes
- Fixed many initial display bugs
- Filter improvements
- Bookmark logic improvements
- Backend process management on quit
- Various UI bugs
v0.9.130alpha - 2026-03-24
Type: major (alpha)
✨ Features
- Alpha release
- Initial working binaries (Windows, Linux, macOS)
- Application framework
- Backend Python service (snitch.py)
- Frontend Electron UI
- Filter system
- Bookmark system
- Hex grid payload viewer
- Info panel
- Statistics
- GeoIP integration
- Hostname resolution
- Squirrel installer integration
- OS detection
- Icons and branding
- Build configuration
Initial Development - 2026-02-17 to 2026-03-24
Type: pre-alpha
✨ Features
- Initial project commit
- Project scaffolding
- Basic Electron + Python backend architecture
- Working on bookmark dropdown and its relative functions
- Now full packet data works in JSON and Prev/Next works properly
- Added next/prev buttons to walk through packets
- Some logo stuff for the real logo (not the placeholder)
- Fixed some things and changed color scheme
- Worked on getting divs and fonts right on frontend
