SAZ Viewer is a local command-line tool and Windows desktop app that read Fiddler SAZ archives and HTTP Archive 1.2 (.har) files. The CLI writes a single portable HTML report; the desktop app shows captures in native WPF views with the same features and can export the identical report. Neither has a runtime dependency on Fiddler Classic, FiddlerCore, a browser library, or a network service. Captured values are safely encoded or inserted as text nodes, and the report's Content Security Policy blocks network access and captured active content.
New to the codebase? Start with the architecture guide, which links component, data-flow, MAPI, browser-runtime, security, testing, and extension diagrams.
- Windows with the .NET 8 SDK or newer to build
- A current Microsoft Edge or Google Chrome release to open the generated report
- For the desktop app: the .NET 8 Desktop Runtime, plus the Microsoft Edge WebView2 Runtime (preinstalled on current Windows 10/11) for the sandboxed WebView tab only
dotnet build .\SazViewer.sln
dotnet test .\SazViewer.slndotnet run --project .\SazViewer.Cli -- .\capture.saz
dotnet run --project .\SazViewer.Cli -- .\capture.har .\reports\capture.html
dotnet run --project .\SazViewer.Cli -- .\capture.saz .\reports\capture.html
dotnet run --project .\SazViewer.Cli -- --password-stdin .\encrypted.saz .\reports\encrypted.html
dotnet run --project .\SazViewer.Cli -- --scrub-auth .\capture.saz .\reports\shareable.html
dotnet run --project .\SazViewer.Cli -- --helpWhen no output path is supplied, the report is written beside the input capture with an .html extension. Capture type is detected from the ZIP/JSON content as well as the extension. --password-stdin applies only to encrypted SAZ files.
To export a HAR from Edge or Chrome, open DevTools, select Network, reproduce or reload the traffic, then use Export HAR from the Network panel. HAR files routinely contain cookies, bearer tokens, query secrets, submitted form values, and response data even when the browser labels an export as sanitized. Treat the original as sensitive and use --scrub-auth before sharing a generated report.
Use --scrub-auth (or PowerShell-style -ScrubAuth) before sharing a report. It runs after HTTP, WebSocket, and MAPI parsing, replaces recognized credentials with typed markers such as [REDACTED:Bearer], [REDACTED:Cookie], and [REDACTED:AzureSAS], and prints marker counts without printing secret values. It covers authentication and API-key headers, all cookie values, sensitive URL parameters, structured and plain-text bodies, WebSocket payloads, MAPI values, and report metadata. If compressed, binary, or otherwise opaque retained bytes cannot be rewritten safely, they are removed and the affected view shows a scrub-removal note. The compact report banner starts collapsed with the total replacement count; its accessible Details control reveals per-type counts. Expanded/collapsed state is safely persisted and synchronized across report tabs and inspector popups when browser storage is available, otherwise each load starts collapsed. Detection intentionally favors over-redaction, but the output should still be handled according to the capture's data classification.
Encrypted archives are detected automatically. In an interactive terminal, the tool prompts with Password: without echoing characters and allows up to three attempts. For automation, --password-stdin reads exactly one line, preserves meaningful leading/trailing spaces, accepts at most 1,024 characters, and does not retry. Supply that line from a trusted secret provider rather than a command-line argument, for example:
Get-Secret -Name SazArchivePassword -AsPlainText |
dotnet run --project .\SazViewer.Cli -- --password-stdin .\encrypted.sazDo not use a literal password in echo, command arguments, scripts, or shell history. SAZ Viewer intentionally has no --password <value> option. .NET and the ZIP library ultimately require an immutable string for decryption, so the tool clears its mutable input buffers promptly but cannot guarantee complete password erasure from managed memory.
The HTTP session list uses Time, ID, Result, Method, URL, Elapsed Time, Req, Resp columns. Non-URL columns size to the widest value in the report while URL receives the remaining width and ellipsizes overflow. Result contains only the numeric HTTP status, or an em dash when no response was captured; the response reason/error remains available as a tooltip. Elapsed Time is Fiddler's Overall Elapsed (ClientDoneResponse - ClientBeginRequest) in integer milliseconds, or an em dash when either timer is missing, invalid, or reversed. Search includes Result and Elapsed Time as well as the existing method, URL, content type, endpoint, protocol, and WebSocket values. Hide CONNECT excludes CONNECT sessions case-insensitively and composes with text/status/protocol filters, Previous/Next navigation, and inspector-only popup state.
In the report, select an HTTP row with the mouse or keyboard to open a full-screen inspector dialog. The session table stays behind the dialog with its filter and scroll position preserved. The dialog shows Previous/Next controls that step through the currently visible/filtered rows in chronological order (disabled at the first/last row, plus an Alt+Left/Alt+Right shortcut), a position indicator, and an accessible Close control (also closable with Escape, which restores focus to the originating row). Each session has top-level Request and Response tabs, with Request selected first whenever the dialog opens or moves via Previous/Next. At 900 pixels and wider the inspector defaults to split Request/Response panes; below 900 pixels it defaults to the single-side view. The compact layout icon beside Previous/Next switches modes, and the redundant top-level Request/Response tabs are hidden while both panes are visible. Wide split panes are side by side with a pointer- and keyboard-resizable divider; narrow split panes stack vertically and disable the divider. Wide and narrow single/split choices are persisted independently through safely guarded localStorage, synchronized across open report tabs, and shared by main and inspector-only popup views. Crossing the 900-pixel breakpoint applies that width class's remembered choice or its default. During migration, a legacy saz-viewer.http-layout.v1 value of split becomes the wide preference; legacy single is ignored so the new wide split default takes effect. When storage is unavailable, switching remains usable in the current page and each reload uses the width-class default. Within each visible side, a fixed-order tab strip — JSON, XML, MAPI, Image, WebView, HexView, Auth, Headers, Raw — fills the remaining space, with unavailable tabs shown disabled. Every available secondary view has a Copy control; JSON/XML copy their complete Formatted Text representation regardless of the active Tree/Formatted Text subview, MAPI copies a deterministic bounded text form of the complete retained protocol tree, and Headers/Raw preserve the report's bounded captured/decoded representation. Image is available only for a complete retained, structurally validated PNG, JPEG, GIF, WebP, BMP, or ICO body (never SVG), renders exclusively from a short-lived local Blob URL, and warns when the captured MIME disagrees with the validated byte signature. WebView is available only for complete retained HTML/XHTML-like text within the existing 64 KiB body limit. It reconstructs an inert structural document through a conservative client-side tokenizer and element allowlist that removes captured attributes, scripts, styles, forms, metadata, SVG/MathML, frames, and embedded resources, then renders it lazily in an opaque-origin iframe with an empty sandbox permission list and a deny-all internal CSP. The frame is removed on every view, side, session, popup, or inspector transition; its Source mode and active-view search use the original decoded source as text without weakening the sandbox. HexView formats the exact retained pre-decoding body bytes with offsets, 16-byte rows, and an ASCII gutter; when successful HTTP decoding produced distinct retained bytes, its compact source selector can switch to that decoded representation, with byte provenance and truncation labeled explicitly. Auth appears only for captured Authorization/Proxy-Authorization/WWW-Authenticate/Proxy-Authenticate headers, starts conservatively redacted, and requires an explicit per-view Reveal action before full values are displayed or copied; reveal state clears on every transition and is never persisted. Full captured header values remain present in Headers and Raw by design. Each visible pane has its own active-view search, which highlights case-insensitive literal matches only in that pane's current Tree/Formatted Text/Source subview, with wrapped Previous/Next and Enter/Shift+Enter navigation; searches are capped at 5,000 matches, remain independent in split view, and clear when their associated view, session, or inspector changes. Tree matches reveal collapsed JSON/XML/MAPI ancestors and Raw captured-byte details temporarily, restoring their exact prior state when the query clears or the view changes. JSON and XML tabs offer a default, initially expanded Tree view (individually togglable nodes, keyboard and mouse toggles) plus a Formatted Text syntax-highlighted view, when the body is recognized as valid JSON/XML; both views are rendered with bounded depth/child/node budgets so very large or deeply nested bodies stay responsive and show an accurate truncation note instead of expanding everything. Their compact Expand all, Collapse all, and Copy actions share one toolbar in that order; the expansion actions appear only in Tree mode and move focus to the selected view-mode button before hiding. JSON and XML Trees share the dense MAPI styling, including boxed -/+ expanders, dotted guides, bold accent-colored names, monospace values, and no container counts or bracket decorations. JSON uses parent[index] array labels. XML uses @attr: value, #text: value, #comment: value, and #cdata: value; pure-text elements collapse to name: text, and empty elements are leaves. MAPI keeps its dedicated always-visible protocol-tree controls because it has no Formatted Text subview. The MAPI tab uses a dense, initially expanded, batched protocol tree with contained scrolling; parser kind and @offset +length metadata remain visible on every row, keyboard navigation follows ARIA tree conventions, valid text has control characters visibly escaped, and raw/non-printable nodes use a bounded labeled hex display. Headers shows only the captured header block; Raw shows the original captured headers plus the decoded body text (with decode status/encodings), a bounded hex view for binary bodies, and a compact "Captured bytes" control when pre-decode bytes are available. The initial secondary tab favors MAPI, then JSON/XML, then Raw, then Headers, whichever is available, and every tab strip supports Left/Right/Home/End keyboard navigation with roving focus. Detected MAPI/HTTP and NSPI sessions remain available through the protocol filter and search without a dedicated table column.
The table and inspector both provide a compact light-bulb theme button. With no saved choice, the report follows the operating-system light/dark preference and updates live; the first activation switches to the opposite theme, and later activations toggle Light/Dark. Explicit choices are persisted through safely guarded browser localStorage when available and synchronized across open report tabs. If a browser blocks storage for file:// reports, switching still works in the current tab. To return to the system default, clear the report site's stored browser data; the compact control intentionally has no visible System mode.
If a session has associated _w.txt traffic, the same row opens a WebSocket inspector instead: chronological logical messages are listed on the left in compact ID, Type, Body, Preview columns and the selected message is shown on the right (stacked vertically on narrow screens). The desktop splitter supports pointer and keyboard resizing and remembers its ratio only while that report tab remains open. ID is the logical reassembled-message sequence, Body is the original logical payload byte count, and the one-line Preview is whitespace-normalized and explicitly ellipsized when bounded. The left-pane search filters case-insensitively against retained decoded text/JSON payload content only; binary data and any bytes omitted by safety truncation are not searched. The selected-payload search highlights bounded, case-insensitive literal matches in the active JSON Tree/Formatted Text, Text, or Raw view, with wrapped Previous/Next navigation; matching uses browser toLowerCase() behavior and is capped at 5,000 results. Client-to-server messages use a blue up arrow and accessible direction label; server-to-client messages use a green down arrow and label. Fragmented data messages are reassembled independently per direction while interleaved Ping, Pong, and Close frames stay visible. JSON, Text, and Raw tabs expose bounded safe views and Copy controls; Raw retains timestamps, fragmentation state, warnings, and underlying-frame IDs/metadata plus text or hex payload representation. Invalid, orphaned, unfinished, truncated, or unsupported frames remain visible with contextual warnings. Previous/Next may move between HTTP and WebSocket inspectors without changing the filtered table order. Open in new tab preserves the current filter and inspector type; after a successful handoff the original tab returns to the table, while a blocked popup leaves the original inspector open.
SazViewer.App is a WPF (net8.0-windows) app that shows each capture in native WPF views with the same behavior as the HTML report: the session grid with filters, search and Hide CONNECT; the inspector with Previous/Next, split view and per-side JSON, XML, MAPI, Image, WebView, HexView, Auth, Headers and Raw tabs; per-pane search and Copy; and the WebSocket inspector. Export HTML still writes the CLI's exact report. See Desktop app: native views for the architecture.
The app icon is an original design: a capture sheet listing sessions under a magnifier. SazViewer.App\Assets\Generate-Icon.ps1 is its source; it uses only System.Drawing to write the SVG, a 256 px PNG and SazViewer.ico (16–256 px). The icon is embedded in the exe (Explorer, the taskbar and the .saz/.har associations' DefaultIcon) and is set on the main and pop-out inspector windows. To change it, edit the script and run pwsh -File .\SazViewer.App\Assets\Generate-Icon.ps1.
dotnet run --project .\SazViewer.App
dotnet run --project .\SazViewer.App -- .\capture.saz .\other.har
dotnet build .\SazViewer.App -c Release # framework-dependent SazViewer.App.exe in bin\Release\net8.0-windows
# Faster startup: ReadyToRun-precompiled, framework-dependent build in .\publish-app
dotnet publish .\SazViewer.App\SazViewer.App.csproj -c Release -r win-x64 --self-contained false -o .\publish-appFeatures:
- Tabs. Each capture opens in its own tab. Captures come from File › Open… (
Ctrl+O, multi-select), by dragging one or more.sazor.harfiles onto the window, from Open Recent, or as path arguments (SazViewer.App.exe a.saz b.har, up to 32;--helplists usage). Opening a file that is already open focuses its tab instead. The tab strip is hidden when exactly one capture is open and its space is reclaimed; the title bar then identifies the file (including the(scrubbed)suffix). The strip appears for two or more captures, with file-name headers and full-path tooltips. Close the active capture with File › Close tab orCtrl+W(Ctrl+F4); when the strip is visible its ✕ button and middle-click also work. Switch tabs withCtrl+Tab/Ctrl+Shift+Tab(Ctrl+PageDown/Ctrl+PageUp); these are harmless no-ops with one capture. Export applies to the active capture. Parsing runs off the UI thread while the status bar shows a busy indicator, and each session's views are built only when it is selected; opens, reloads and exports are serialized, so password prompts never overlap. - Memory. Each tab holds its parsed model; per-session views are created on demand and released when the selection changes. A WebView2 exists only while a WebView tab is showing a rendered preview. Closing a tab disposes its model, view-models, file watcher and pop-out inspector windows.
- Single instance. If SAZ Viewer is already running for your account, a second launch (for example, double-clicking a
.sazor.harfile) forwards its paths to the running window and exits. The running window opens them as tabs and comes to the foreground. Only existing, fully qualified capture paths are accepted, and passwords are never forwarded. - File association (per user, no admin). Tools › Register as .saz/.har handler adds SAZ Viewer as an Open with choice for both capture types under
HKCU\Software\Classes. This creates the sharedSazViewer.CaptureProgID,.saz\OpenWithProgidsand.har\OpenWithProgidsentries, and anApplications\SazViewer.App.exeentry. It does not replace either current default. Windows 10/11 may still ask you to confirm in Open with › Choose another app (tick Always), and the app offers to open Settings › Default apps. The menu item is checked while both types are registered. If the exe has moved or one registration is missing, the item reads update moved app path. Unregister removes only the keys and values the app created and leaves other handlers intact. - File changes. Each tab watches its file, with debouncing and support for editors that save through a temp file and rename. When the file changes, the tab shows This file changed on disk. with Reload and Dismiss. Reload re-parses off the UI thread and asks for the password again for an encrypted capture. It keeps the tab and its position. If the reload fails, the previous report stays and the error is shown. A deleted file shows File no longer exists, and the loaded report stays viewable.
- Payload search. Enable Search payloads beside the session search box to include request/response headers, decoded HTTP bodies, WebSocket text and UTF-8-decodable binary messages, and already-decoded MAPI tree names/values. It is off by default and remembered for future captures. Searches are debounced, cancellable, run with bounded parallelism, and show progress and the final match count. HTTP scans use the parser's 4 MiB decoded-body safety ceiling; sessions that reach a body/message/tree limit are reported. Decoded folded text is held in a per-capture 256 MiB LRU, so later queries are faster. Scrubbed captures index only their scrubbed models and cannot match removed credentials. The HTML report is unchanged: it embeds bounded display payloads rather than the archive/deferred decoder, so equivalent full-capture search is not a trivial runtime-only addition.
- Encrypted captures prompt with a modal masked password dialog, allowing up to three attempts like the interactive CLI. Passwords are never logged, persisted, or accepted as arguments; the dialog copies the
SecureStringinto a mutable buffer that the archive layer clears after use (the same managed-stringcaveat as the CLI applies). - File › Export HTML… writes the report byte-for-byte as
saz-viewer capture.saz out.htmlorsaz-viewer capture.har out.htmlwould. Export scrubbed HTML (remove credentials)… matchessaz-viewer --scrub-auth; it re-parses the capture (re-prompting for an encrypted SAZ capture's password) because scrubbing rewrites the in-memory model. Export refuses to overwrite the source capture. - File › Open Recent lists up to 10 captures, most recent first, stored as full paths only in
%LOCALAPPDATA%\SazViewer\recent.json. Missing files are removed with a message; Clear recent files empties the list. A corrupt or unreadable file is ignored. - Options. The keyboard-accessible Options menu applies app-wide choices immediately and stores them in
%LOCALAPPDATA%\SazViewer\preferences.json. Theme selects System (the default), Light, or Dark; the light-bulb button uses the same preference and stays synchronized. Windows high contrast always overrides the selected palette. Hide CONNECT on open sets only the initial filter for captures opened afterward; changing a tab's filter does not change this default. - Session columns. Drag headers to reorder them and drag header dividers to resize them; order, width, and visibility are stored in
preferences.jsonand shared by every open capture. Options › Columns… opens the searchable keyboard-accessible chooser, while right-clicking a header provides quick visibility toggles, More columns…, and Reset columns to default. The default remains Time | ID | Result | Method | URL | Elapsed Time | Req | Resp. Additional groups cover URL/content/cache details, every common Fiddler timer plus derived TTFB/server/download durations, connection/process/TLS/session flags, and WebSocket/MAPI/auth summaries. Custom request-header, response-header, and session-flag columns may be added repeatedly. Sensitive custom header values are masked in normal captures (Bearer [hidden]or[hidden]) and display the scrubbed marker in scrubbed captures. Values are extracted from headers and metadata or already-decoded bodies only, so adding columns never forces deferred body decoding; rows and columns remain virtualized and overflow scrolls horizontally. The HTML report intentionally keeps its portable fixed schema; configurable report columns are feasible later, but require an explicit export configuration rather than silently coupling a report to this machine's desktop preferences. - Grid width. The default URL column fills the remaining full-window grid width. Entering Right pane mode locks that full-window width before narrowing the grid, so every column stays exactly as wide as it was in Bottom pane mode and the slim horizontal scrollbar exposes overflow instead of squashing or over-widening columns. A manually resized URL remains fixed at the saved width in every viewer mode.
- Search fields. Session, inspector, WebSocket, column-chooser, and advanced-filter search/value fields share the same themed placeholder and clear button. The button appears only while text is present; selecting it or pressing Esc clears the query, restores any tree expansion changed by search, and leaves focus in the field.
- Advanced filters. Select the funnel icon, View/Options › Advanced filter…, or press Ctrl+Shift+F to open a reusable, resizable filter window for the active capture. Its size and position are remembered; reopening focuses the same window, while closing or reloading its capture closes it. The toolbar keeps a compact result count and accent badge while a filter is active. Build typed rules over every built-in/custom column plus lazily decoded request and response bodies. Rules support AND-before-OR precedence, up to three nested parentheses per row, disabled rows, case sensitivity, numeric/date/time/size comparisons (
KB,MB, and duration suffixes), status ranges, comma/newline lists, and bounded .NET regular expressions (1,024-character pattern, 1 MiB input, 100 ms match timeout). Contains tests whether the field contains the entered value; IsIn tests whether the entered text/list contains the complete field value; IsOneOf performs an exact comparison against comma- or newline-separated items. TheirNotforms invert the same test. Missing values match only Is empty, never a comparison or negative operator. Advanced results are ANDed with quick search, status/protocol and Hide CONNECT filters. Body evaluation runs in the background with progress/cancel and reuses the 256 MiB payload cache; scrubbed tabs evaluate only scrubbed models. The current rules remain with their capture tab, and Restore last recalls the last applied filter from this app session. Named filters are stored in%LOCALAPPDATA%\SazViewer\filters.json; the window can load, replace, delete, and import/export strict data-only.sazfilter.jsonfiles. - Inspector layout. Options › Default inspector layout selects Automatic by window width (the default, with the existing independently remembered wide/narrow choices), Always split, or Always single. The toggle next to Previous/Next remains available. In Automatic mode it updates the remembered choice for that width class; with an Always setting it temporarily overrides that inspector until the option changes or the capture closes. Narrow split views still stack Request and Response. Open in new window continues to create an extra independent pop-out.
- Session viewer location. Options › Open session viewer in chooses Bottom pane (default), Right pane, or a reusable New window per capture tab. Bottom pane places the grid above the viewer; Right pane places the grid on the left and always stacks HTTP Request/Response and WebSocket list/detail vertically in the viewer. The outer bottom/right splitter sizes and the Right-pane Request/Response split are remembered independently. In New window mode the embedded pane is hidden and the grid gets the space; the window follows selection without taking keyboard focus from the grid, retains Previous/Next and shortcuts, and reopens on the next selection after it is closed. Switching the option rehosts the open inspector cleanly.
- Theme. The light-bulb button switches light/dark; choose Options › Theme › System to resume following Windows. Windows high contrast is honored. Native views use shared slim, arrowless scrollbars whose thumb, track and corner update with the active theme; high contrast uses Windows system colours.
- File › View scrubbed (redact credentials) reopens the active capture with the same credential scrubbing as
--scrub-auth, so every view shows[REDACTED:…]markers, with a collapsible banner of per-type counts. - Theme, inspector defaults, remembered automatic layouts, session-viewer location and splitter proportions, Hide CONNECT-on-open, payload-search choice, and banner choices are stored in
%LOCALAPPDATA%\SazViewer\preferences.json. Existing preference files are upgraded in place without discarding their prior fields.
Security notes:
- Captured values are shown only as text in native controls, within the report's size, tree and search bounds. Nothing is written to disk unless you export.
- The WebView tab is the only HTML surface. It renders the report's sanitized inert document in a WebView2 with scripts, host objects, web messages, context menus and (in Release) DevTools disabled. The document is served from memory at
https://webview-preview.sazviewer.invalid/document.html, every other request is answered with403before leaving the process, and any navigation, pop-up, download, permission or external-protocol request is refused. The WebView2 is created only while that tab is shown and disposed when you leave it.- Single-instance hand-off uses a per-userLocal\mutex and a named pipe. The pipe's ACL grants access only to the current user and denies network logons, and the client also checks that the pipe is owned by the current user. Requests use a small versioned binary header and a strict JSON{"paths":[…]}body capped at 256 KiB, 32 paths and 2,048 characters per path. Anything else is rejected. The receiving instance then opens only existing, normalized.sazpaths. - WebView2 browser data lives in the per-user
%LOCALAPPDATA%\SazViewer\WebView2folder.SAZVIEWER_DATA_DIR(fully qualified) redirects the whole%LOCALAPPDATA%\SazViewerroot and exists for isolated test runs. The single-instance names include a hash of this folder, so test runs never forward to your real instance.
Manual check: run dotnet run --project .\SazViewer.App -- .\capture.saz, then:
- Confirm the title and status-bar counts, search columns and payloads, open a session, switch split/single view, use Open in new window, toggle the theme, try File › View scrubbed, and reopen the app.
- Drag two captures onto the window and check that you get one tab each and that a duplicate focuses the existing tab. Click into the grid and use
Ctrl+Tab,Ctrl+Shift+TabandCtrl+W. - Launch
SazViewer.App.exe other.sazagain. It should exit and the first window should gain a tab. - Register, double-click a
.sazin Explorer (confirming Open with if asked), then unregister and check thatHKCU\Software\Classes\SazViewer.Captureis gone. - Save a changed copy over an open capture, click Reload, and delete the file to see the notice.
- Open an encrypted capture. Three wrong passwords show an error.
- Compare Export HTML and Export scrubbed HTML with the CLI output using
fc.exe /b.
dotnet publish .\SazViewer.Cli\SazViewer.Cli.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o .\publish
.\publish\saz-viewer.exe .\capture.saz .\capture.htmlUse win-arm64 instead of win-x64 for Windows on ARM. The published executable includes the .NET runtime; only the generated executable is required to run the tool. The desktop app is built framework-dependent only (publishing it with -r enables ReadyToRun for faster startup; see Startup); MSIX and single-file packaging are not provided yet.
- SAZ files are treated as ZIP archives, and sparse
raw/<id>_*entries are supported. - HAR 1.2
log.entriesare mapped into the same session model, including request/response headers, cookies, query and post data, decoded text/base64 bodies, redirects, timings, page/initiator metadata, and Chrome/Edge_webSocketMessages. HAR bodies are already decoded;Content-Encodingis preserved as metadata but is never applied a second time, and HexView labels the retained representation as Decoded. - Unencrypted ZIP/SAZ files use the .NET archive reader. Encrypted files support the format verified from Fiddler Everywhere Reporter exports: WinZip AES AE-2 with AES-256 and DEFLATE, including archives mixed with traditional PKZIP/ZipCrypto DEFLATE members and unencrypted members. Other AES vendor versions, key sizes, vendors, or encrypted compression methods fail with an explicit unsupported-encryption error rather than being guessed.
- WinZip AES members are fully read and their HMAC authentication code is verified before plaintext is passed to the parser. ZipCrypto members are also fully read and CRC-checked, but ZipCrypto is legacy encryption and does not provide modern authenticated-encryption security.
- ZipCrypto's password verifier is only one byte. In the rare case that an incorrect password passes that verifier, an interactive parse may consume another allowed attempt; if no supplied password succeeds, the tool preserves the integrity failure as archive corruption rather than claiming the password was definitely wrong.
- Encrypted extraction is memory-only and transactional. Safety limits are 100,000 archive entries, 256 MiB decompressed per encrypted entry, 1 GiB cumulative decrypted data, and a 1,000:1 declared compression ratio. A non-seekable input stream is copied only up to 512 MiB to enable central-directory validation.
- Individual missing or malformed records produce warnings instead of aborting the archive.
- Session-specific warnings remain visible in the affected session inspector. The aggregate warning section is currently omitted from generated HTML, while warning data remains available to the CLI/report model.
- HTTP body previews are bounded. Text uses a safely recognized charset; binary data is shown as a bounded hex preview.
- Declared chunked transfer framing is removed before content decoding.
gzip, zlib-wrappeddeflate(with a raw-DEFLATE compatibility fallback), andbrare supported, including chained and repeatedContent-Encodingfields in HTTP decoding order. - Decoding is transactional and bounded: an HTTP entry is read through a 4 MiB protocol-payload limit plus bounded headers, each decoded stage is limited to at most 4 MiB and 100x expansion (with a 1 MiB floor), gzip content is capped at 128 members, displayed decoded bodies remain capped at 64 KiB, and captured encoded-byte views are capped at 16 KiB. Unsupported, corrupt, incomplete, or oversized content remains available as bounded captured hex with a warning.
- The inspector keeps captured headers unchanged. Successfully decoded bodies are shown as decoded text in the Raw tab (with decode status and removed encodings noted), and a compact "Captured bytes" control reveals the original pre-decode bytes when available.
- Each HTTP session has one versioned, bounded gzip envelope containing ordered request/response headers and retained captured bytes, plus decoded bytes only when they differ. Headers, Raw, JSON/XML formatting and trees, Auth redaction, Image, WebView, HexView, search text, and copy text are derived locally on demand rather than duplicated in the HTML. MAPI protocol trees and per-session WebSocket models remain independently compressed. HexView displays at most 1 KiB per captured/decoded representation and labels retained prefixes accurately; complete validated images and inert WebView source use the existing 64 KiB decoded presentation window, while incomplete bodies stay disabled. No payload is fetched from the network. Browsers without the required local
DecompressionStreamsupport show an actionable session error instead of a blank view. - Without
--scrub-auth, report generation is unchanged. With it, parsed model values are scrubbed before any HTML or compressed payload is serialized, including session-list URLs and bytes used by Raw, HexView, Image, WebView, Auth, search, and copy. Original compressed bytes are not retained when only decoded content can be safely rewritten. - WebSocket records retain Fiddler ID/flags, direction, FIN/opcode/masking, lengths, timestamp, and warnings. Logical data messages are reassembled within explicit limits: 256 MiB uncompressed per
_w.txtentry, 10,000 records, 16 MiB aggregate retained frame payload, 1 MiB retained per logical message, 5,000 displayed messages, and a 32 MiB decoded per-session report envelope. Limit hits remain visible as contextual warnings; there is intentionally no separate aggregate WebSocket section below the HTTP table. - The tool does not execute captured content and does not make network requests.
The WebSocket reader follows the mixed pseudo-header/binary record layout described by Fiddler's author, accepts the leading blank-file-header and known synthetic-header variants, and decodes each declared-length frame according to RFC 6455. Unknown or malformed variants are retained as bounded undecoded summaries with warnings.
Concatenated gzip members are supported. The raw-DEFLATE compatibility path has no format checksum, so it provides structural validation but cannot provide the integrity guarantee available for gzip and zlib-wrapped DEFLATE.
MAPI parsing uses the fully normalized HTTP entity bytes, not the 64 KiB display preview. Detection is based on application/mapi-http, X-RequestType, and X-ResponseCode. Parser state and budgets are capture-local; payloads are capped at 4 MiB, protocol trees at 25,000 nodes and depth 64, collections at 100,000 items, strings at 1 MiB, and retained raw nodes at 16 KiB.
Implemented transport coverage includes all 23 X-RequestType request and response envelope schemas, response additional headers/status, mailbox and NSPI structures, all 32 property-value forms, all 12 restriction forms (including SubObject, which upstream omits from its dispatcher), all 11 rule-action types and payload layouts, all 572 PidTag, 365 PidLid, and 131 string-named upstream property symbols, chained MS-OXCRPC extended buffers, XOR 0xA5, bounded Direct2/LZ77 decompression, ROP list/handle-table framing, all 132 upstream ROP names, and all 40 upstream auxiliary payload dispatch mappings plus MS-OXCRPC AUX_TYPE_SERVER_CAPABILITIES, and safe unknown/raw fallback. Individual ROP semantics cover all 128 request and all 131 response dispatcher cases: 49 request/50 response fixed-width schemas plus 79 request/81 response variable, self-contained decoders for the MS-OXCFOLD/MS-OXCTABL (folder/table), MS-OXCPRPT/MS-OXCSTOR (property/stream/store), MS-OXCMSG/MS-OXORULE/MS-OXCPERM/MS-OXCNOTIF (message/rule/permission/notification), and MS-OXCFXICS (FastTransfer/ICS) families, dispatched centrally per operation.
Capture-local FastTransfer state resolves each buffer-local handle index to a logical MAPI connection plus 32-bit server object handle, allowing bounded varSizeValue continuation across separate HTTP round trips while preventing joins across unrelated connections or reused handles. It tracks exact recursive marker pairs, classifies standalone ICS productions, and decodes context-special ProgressInformation and PropertyGroupInfo/PropertyGroup/GroupPropertyName structures, including bounded split values. Successful configuration ROPs select contentsSync, hierarchySync, state, messageList, and topFolder roots transactionally and validate their productions across buffers. Successful same-capture Folder/Message/Attachment handle provenance also selects markerless folderContent, messageContent, or attachmentContent roots for source CopyTo/CopyProperties and matching destination configurations, including same-Execute output-slot reuse; failed, missing, conflicting, or malformed provenance remains unvalidated rather than guessed. messageList enforces its optional DnPrefix/EcWarning/message entries and legal unmarked end-of-stream completion. Folder/message/attachment productions enforce recursive properties-before-messages-before-subfolders order, the exact two deletion-marker-delimited folder message lists plus optional trailing deletion marker before subfolders, message properties-before-recipients-before-attachments order, required attachment number where present, and embedded-message placement; the no-deleted-properties topFolder grammar continues to reject those folder deletion markers. RecoverMode FXErrorInfo unwinds to the nearest retained synchronization or message-list boundary and requires its single binary error property. Phase mismatches stop without speculative resynchronization, and download Done reports incomplete selected roots.
State is completed from authoritative download status and invalidated on failure, RopRelease, or successful handle reuse; upload status is retained but not interpreted because the protocol requires clients to ignore it. When multiple output-producing ROPs reuse one handle-table slot in a single HTTP session, intermediate handles are absent from the final table, so affected root/upload state is discarded explicitly instead of being attached to the wrong server object. ICS UploadStateStream Begin/Continue/End operations are correlated transactionally across HTTP round trips on the synchronization handle, bounded independently, and finalized as REPLGUID IDSET/CNSET data; failed, missing, mismatched, released, or reused operations discard state rather than splicing streams. Cross-operation shapes otherwise use bounded transactional state keyed by exact HTTP round trip or by logical MAPI connection as the protocol requires. MAPI/HTTP Session Context cookies provide the primary capture-local connection identity, including sequence-cookie rollover; Connect/Bind creates an isolated generation, ambiguous cookie matches isolate state instead of guessing, and a successful Disconnect/Unbind removes the connection's retained state and opaque cookie aliases. Lifecycle success requires a successful X-ResponseCode plus zero body StatusCode/ErrorCode; Connect/Bind also require their complete operation fields, while a truncated optional auxiliary suffix does not override an otherwise authoritative outcome. Cookie values are hashed for internal correlation and never emitted in diagnostics. This includes the five dispatcher shapes that require earlier request state, plus server-handle-keyed RopSetColumns reconstruction for RopQueryRows, RopFindRow, RopExpandRow, and RopNotify table rows; column state is committed only after success and invalidated on failure, RopRelease, RopResetTable, or table-handle reuse. RopLogon privacy is likewise committed only after a complete successful response, using the response flags as authoritative. Conflicting or unavailable correlation falls back to raw content plus a warning rather than guessing.
NSPI PtypString8 values use the capture's declared code page; unsupported or unavailable code pages retain bounded raw bytes and mark the parse partial. Deterministically self-delimiting nested PtypRestriction and PtypRuleAction values are decoded in generic ROP and NSPI property paths; PtypServerId is decoded consistently in ROP, NSPI, and FastTransfer paths, while client-defined ServerId payloads remain bounded raw by definition. Multi-valued properties accept only the 12 element types for which MS-OXCDATA defines a multiple form; invalid combinations fail closed because their boundary cannot be determined safely. Rule-recipient AddressBookEntryIDs and ModLinkAtt permanent/ephemeral EntryIDs decode their exact bounded semantic fields, validate the required MUIDEMSAB provider where applicable, and fall back locally to raw on malformed content. Rule-action Folder/Message EntryIDs also expose their StoreObjectType name alongside the numeric value. Extended-rule EntryID sizes and recipient/property counts follow the 32-bit MS-OXORULE definitions, correcting narrower reads in the pinned upstream implementation. Auxiliary-buffer offset-referenced trailer strings/bytes are resolved from validated, bounds-checked offsets rather than upstream's unchecked sequential pointer arithmetic. Unknown generic property types and RopReadRecipients row properties without a deterministic column source remain explicit bounded raw fallback. Missing, malformed, mismatched, retried, and partially accepted state-changing operations are audited transactionally: uncertain outcomes invalidate the affected retained context, and queue mismatches discard later staged operations rather than shifting attribution. Differential validation includes the legal synthetic suite and a 1,172-session local corpus; Edge exercises compressed lazy MAPI rendering at the full 25,000-node parser budget, plus narrow and popup inspector modes. See docs/mapi-parity.json for exact counts and protocol-information limitations.
The implementation was independently adapted with reference to the MIT-licensed Office Inspectors for Fiddler source at commit c18dd66c99f3b5a96c2e1d31698c5cf2deb828e7 and Microsoft Open Specifications. See THIRD-PARTY-NOTICES.md. No Fiddler/FiddlerCore, HexBox, Outlook interop, EQATEC, Ionic Zip, or other upstream binary is included.