Document analysis
What this module is
Malwagon document analysis takes one document file, from the office, portable document, rich text, notebook, compiled help or markup families, and opens it in a virtual machine under the application that owns the format. It returns a structural reading of the file, the recorded behaviour of that application, an ATT&CK-mapped finding list, a score and a verdict.
A document built to carry an attack is what analysts call a maldoc, and maldoc analysis is the work this module does. The defining property of a maldoc is that its code belongs to the application: the macro, the embedded object, the field command and the attached template all fire because the office application, the reader or the help viewer does what the format asks of it. Every design decision on this page follows from that. The file is read apart on the analysis host first, then opened in the guest the way the person who received it would open it, and the report keeps those two readings as separate grades of the same claim.
- Submission
- One upload, or one link the analysis host downloads for you. 128 MB from the console, 100 MB on the public landing scan, empty files refused. A download that fails refunds the credit.
- Formats
- 29 extensions across the OLE, OOXML, RTF, PDF, notebook, compiled-help and web-container families. Content decides admission; the name decides it only where the format has no trustworthy header.
- Protected delivery
- An archive is refused unless you supply its password or ask for automatic unpack, because that is the phishing case. The document inside is what detonates; the wrapper is packaging.
- Supporting files
- Up to 10 more, 32 MB each and 64 MB together, written into the same guest folder as the document before the machine is powered on: a template, a data file, a dependency.
- Image
- One, and it is not a preference. The Windows build carrying the office applications, enforced on the submission itself, so a hand-built request cannot reach another image.
- Run length
- 10 s to 600 s, capped by plan at 120 s free and 300 s on Pro. Opened automatically, or left for you to open by hand over the live view.
What the module takes in, and what it refuses
The name on a submission decides nothing. Acceptance is settled on content
first: the portable document header, the {\rtf opening group, the
OLE compound-file signature, the notebook file-data-store GUID, the compiled
help signature, and a ZIP only when it also carries an OOXML part marker.
Formats that have no header worth trusting, which in practice means the markup
containers and the older publishing formats, are admitted on extension alone
and the report says so by leaving their format unclassified.
Refusal matters as much as admission. A submission whose header reads as a
native executable or a shebang script is rejected outright, with the error
naming the module it should have gone to. An invoice.docx
carrying a PE does not become a run titled Document analysis: the header
decides where it goes, and the error points at the file module.
| Family and extensions | Recognised by | Format-specific analysis | Opened |
|---|---|---|---|
Legacy OLE documents.doc .dot .xls .xlt.ppt .pps .pot |
Compound-file signature | Macro project, auto-execution triggers, legacy macro sheets, the embedded object pool, container encryption | Yes |
OOXML packages.docx .docm .dotm.xlsx .xlsm .xlsb .xltm.pptx .pptm .ppsx .potm |
ZIP plus an OOXML part marker | All of the above, plus relationship analysis, embedded object parts, and a rebuild of the package without its macro storage | Yes |
Rich text.rtf |
{\rtf header |
Embedded object parsing and field analysis. Not routed down the macro path, because the format carries no macro project | Yes |
Portable document.pdf |
%PDF- header |
Keyword census, trigger correlation, URI, launch-target and embedded-name extraction, over the raw file and its inflated object streams | Yes |
Notebook.one |
File-data-store GUID | Attachment carving, with each attachment typed by its own content | Yes |
Compiled help.chm |
Container signature | Shortcut-control heuristics, extracted command lines, references to signed built-in binaries | Conditional |
Markup containers.htm .html .mht .xml |
Extension only | None. The format reads unclassified; metadata and the general static chain still run over the bytes | Yes |
Publishing and diagram.pub .vsd .vsdx |
Extension, with the container read where there is one | Whatever the underlying container exposes. No analyser is written for these formats themselves | Yes |
The last column describes the guest, and only the guest: it says whether the file is opened through its shell association, in the application that owns the format. Compiled help is the one conditional entry, because the guest's own document-extension list does not carry it, so it is opened that way only when the sample was typed as a document to begin with.
Typing is done once, and it belongs to the bytes
Sample records here are content addressed and shared by every future scan of the same bytes, so the row is typed by what the file is. The module a submitter happened to pick has no say in it. A label chosen at submission time does not travel to the next analyst who scans those bytes.
An OOXML document is a ZIP, and that makes the automatic archive unpacker a hazard here: taken apart, a macro-enabled package reads as a handful of XML parts nobody submitted, and the macro storage stops being the subject of the scan. The unpacker is therefore forbidden from opening a document, and a test holds the guard in place. The mirror case is handled too, because an encrypted OOXML file is not a ZIP at all but an OLE container holding an encrypted package, so it is recognised by name and container together and stays with the document engine, clear of the binary path.
A document that arrives with no usable extension is stored under the one its content implies, worked out from the container: the header for portable documents, rich text, notebooks and compiled help, the directory entry names for the legacy OLE family, and the part names for OOXML, where the presence of a macro storage is what separates a macro-enabled package from a plain one. The scan keeps the name you submitted. The guest gets a name it can open.
Password-protected archive delivery
The pattern where the password sits in the body of the phishing mail is handled end to end. Supply the password, or tick automatic unpack, and the archive is opened on the analysis host under the same bounded containment as every other parse; the document inside becomes the sample and is what reaches the guest. Without one of those two, an archive is refused outright and the error says why. Quietly reading a container as though it were the document it hides is the failure this rule exists to prevent.
Why is there no image selector on this form?
Because the document has to open in the application that owns its format, and one image carries those applications. A macro is not code this platform runs. It is code the word processor runs. An embedded package is only written out and launched if the object host is there to unpack it; a remote template is only fetched if the application resolves the relationship; a field command only executes because the field engine evaluates it on open. Every technique this module exists to observe needs its interpreter present, which makes the guest image a control variable in the experiment.
So the operating system field on the document form is single choice and pinned to the build carrying the office applications. A different image in the submission is a validation error. The alternative is a run on a machine with nothing installed to open the file, which produces a clean report about nothing. Two more things stand behind that field: a plan-level allowlist of images, and an image record that only advertises office support after the word processor was actually found on that disk. The execution-privilege selector is gone for a related reason. A document is opened at the interactive user's ordinary token, the way it would be by the person who received it, and the run records the token it really got. A privilege choice there would make the run less like the incident it is modelling.
Opened the way a recipient opens it
A document is not a program. Hand one to the loader as if it were, and no macro execution follows: Windows declines to start a file that is not a PE, so nothing opens the document and the run finishes clean. A report about nothing having happened is the expensive kind of false negative, because it looks like an answer. So the file keeps the extension its content implies and is opened through its shell association, on an image prepared so the document opens into its full editing surface, since a read-only preview holds the code back, and so nobody has to click a prompt for the sample to reach its own code.
What maldoc analysis reads off the document before it opens
The static read runs on the host while the guest is restoring and arming, so it costs the analyst no wall-clock time. No file you submit is executed there. The document is parsed, and parsing attacker-supplied container formats is itself the exposure, so the whole parse runs in a forked child under a wall clock and an address-space ceiling, and every archive read is budgeted from the container's own directory before a single part is materialised. A part that declares gigabytes does not get to allocate them. When a parse does breach its budget, that is written into the report as a finding about a resource bomb. The alternative, a document that quietly turns out to have no macros in it, is a lie the analyst has no way to catch.
The macro project
Modules in a recovered project are decompressed and kept with their real
name and true size, plus a bounded excerpt. Auto-execution triggers are named
one by one, each with the procedure that carries it, because
AutoOpen, Document_Open and
Workbook_Open are different statements about how the file behaves
and a single boolean would flatten all three. Together they are what turns the
document into a T1204.002 finding: opening it runs code. Keywords are typed as
they are recorded, so an auto-execution trigger, a suspicious call and a
recovered indicator are three different kinds of row, each labelled with the
kind of thing it is.
Primitives are reported as the primitives actually present. Auto-execution, file write and shell or process execution are each tested separately, and the macro text that set each one is quoted alongside it, so the sentence in the report is backed by the line that produced it.
A auto-execution AutoOpen() W file write Open ... For Output As X shell / process exec not present summary the macro combines auto-execution and file write primitives
Is the macro you can read the one that runs?
A macro project stores two representations of the same code: the compressed source, and the compiled P-code the application executes. They are written together, and nothing keeps them in agreement afterwards. Overwrite the source with something harmless, leave the compiled stream untouched, and a reading of the source describes a benign macro while the application goes on running the original. Malwagon compares the two streams and reports a disagreement as a finding of its own, so the report tells you the readable macro is untrustworthy and declines to repeat what it says.
Containers inside the container
Embedded objects are analysed for both OOXML embeddings and the legacy object pool. Each object's class identifier is resolved against a named table, the original file name an object package carries is recovered, and that name is tested against the 24 extensions that make a dropped package an execution and not an attachment: executables, libraries, installers, shortcut files, script and command types, disc images. A document embedding a portable document is a person attaching a file. A document embedding a shortcut is a delivery mechanism.
| Object class | What it is | How it is weighed |
|---|---|---|
| Equation object | A legacy equation component, resolved from either of the two class ids that register it | Exploit carrier Characteristic of exploit delivery, effectively absent from ordinary authoring, and reported as a structural fact about the file |
| Object package | A generic file wrapper that carries an arbitrary payload and the file name it should be written out as | Drops and launches The packaged name is recovered and flagged when it is a runnable type |
| Script component | A scriptlet object that names a script for the object host to fetch and run | Exploit carrier Remote script execution reachable from an opened document |
| HTML document object | A markup and script host object embedded inside the document body | Exploit carrier Remote markup and script rendered from inside the file |
| Script engine object | An embedded scripting engine instance | Exploit carrier Executes script hosted by the object itself |
| Nested workbook | A spreadsheet embedded inside another document | Carrier May hold its own macro project, so the nesting is what is reported |
| Unrecognised class | A class identifier that is not in the table and is not the null class | Reported by id Named and left unweighted, with the raw identifier printed for you to judge |
Relationships are the other half of the container story. OOXML relationship
parts are read for attached-template targets and for external object, frame,
frameset and link references, and the result is rendered as an external
relationship table with the target shown in full. A target on http, https or
ftp is written into the scan's IOC table. A UNC target is read differently and
deliberately: \\host\share in a template relationship makes the
application authenticate outward to a machine the document's author chose,
which is a credential exposure that costs the attacker no macro at all.
Field commands are kept apart from one another. Detection is anchored on the field structure itself, so the letters appearing loose somewhere in the file prove nothing, and the automatic and manual forms are reported apart, because one runs a command line the moment the file opens with no macro and no user action while the other waits for someone to update the link. That distinction is the difference between a document that is already running and a document that needs a second mistake.
What happens to a document with no macro project?
Portable documents get a keyword census over the raw file and over its inflated compressed object streams, plus URI extraction, launch-action target extraction, embedded file names, remote go-to and form submission. Script presence and an opening or additional-action trigger are combined into a single stated fact, because it is the pair that means code runs when the file is opened, and two separate counts leave the reader to make that join alone.
| Keyword | What it marks | Class |
|---|---|---|
| /JS | A script stream attached to an object | Active content |
| /JavaScript | A script entry in the name tree or an action | Active content |
| /OpenAction | An action performed when the document opens | Trigger |
| /AA | An additional action bound to a page or a field event | Trigger |
| /Launch | An action that starts an external program, with its target extracted | Active content |
| /EmbeddedFile | A file carried inside the document, with its name extracted | Active content |
| /RichMedia | Embedded media handled by a separate player object | Counted |
| /AcroForm | An interactive form, which is where field-level actions live | Counted |
| /JBIG2Decode | A legacy image decoder filter | Counted |
| /URI | An outbound link, extracted and written to the IOC table | Counted |
| /SubmitForm | A form that posts its contents to a remote endpoint | Counted |
| /GoToR | A jump into a remote document | Counted |
Where those keywords are found matters as much as whether they are found. Active content that is invisible in the raw bytes and only appears once the compressed object streams are inflated is reported as hidden, and reported separately from the content itself, with the report saying plainly that hiding corroborates and must not convict. Compressed object streams are also how ordinary authoring tools store objects, so their use is context for a finding and is not a finding by itself.
Notebook files are carved for attachments by their file-data-store marker, and each carved blob is typed by its own content, with the notebook's own claim about it ignored: executable, package, script or command, markup or application file, or plain data. Rich text is parsed for object class names, object sizes, equation objects and embedded executables inside those objects, and it is not sent down the macro path, because the format carries no macro project.
Compiled help is examined for the shortcut control that runs a command when the file opens, for the command line carried in its parameter, and for references to signed built-in binaries. Legacy spreadsheet macro sheets are looked for in modern packages as well as in old binary workbooks, because the modern package is where that technique lives today. And a document that is encrypted is reported as encrypted, as a finding in its own right, because a document nobody can inspect is exactly why the technique is used for delivery.
What the document would hand to a shell
Living-off-the-land abuse is matched over the macro's own string literals, joined the way the macro joins them, so a command assembled from concatenated fragments is recovered as one command and an identifier that merely contains a binary's name is not mistaken for a command line. The corpus is 31 signed built-in Windows binaries, each requiring a distinctive abuse argument rather than its bare presence, and each carrying its own ATT&CK technique: certificate and transfer utilities, the library and control-panel loaders, the script hosts, the build and installation tools, the scheduling and service controllers, the help viewer. The same rules run over a compiled help file's extracted command line.
URLs and IPv4 literals are recovered from both the raw macro source and the assembled-literal view, prefix duplicates are collapsed, and private, loopback and link-local space is excluded so a hard-coded internal address does not arrive in your indicator feed as a network target. What survives goes into the scan's IOC table tagged with the layer that found it, and appears in the report as its own kind of keyword row.
The downloader claim is never made on one signal
A document is only described as fetching and executing a remote stage when corroborating evidence stands behind the claim. Taken singly, the things that point at a downloader are things a legitimate business macro does every day, so the claim is made on the corroboration rather than on any one of them, and the report shows the evidence that carried it.
| Technique | Read from the document | ATT&CK | Surfaces as |
|---|---|---|---|
| Macro project present | Decompressed module source with per-module name and true size | T1059.005 | Indicator, named signature, triage tag |
| Auto-executing macro | The individual trigger procedure names found in the project | T1204.002 | Indicator, triage tag, narrative row |
| Legacy spreadsheet macro sheet | Macro-sheet parts and their content-type declarations, in modern packages as well as binary workbooks | T1059 | Indicator, named signature |
| Compiled and source form disagree | Comparison of the two stored representations of the same module | T1564 | Indicator, named signature |
| Field command execution | Field structure, with the automatic and manual forms separated | T1559.002 | Indicator, named signature |
| Remote template injection | Attached-template relationship targets | T1221 | Indicator, named signature, relationship table, IOC |
| External relationship reference | External object, frame, frameset and link targets, including UNC | T1221 | Indicator, named signature, relationship table, IOC |
| Weaponised compiled help | Shortcut control plus the command line in its parameter | T1218.001 | Indicator, named signature |
| Launch action in a portable document | Extracted launch targets | T1204.002 | Indicator, named signature |
| Structure obfuscation | Active content present only after object streams are inflated | T1027 | Indicator, stated separately from the content |
| Encrypted document | Container-level encryption of the package | T1027.013 | Indicator, and a stated reason the file cannot be read |
| Fetch and execute a remote stage | Corroborating evidence from more than one part of the static reading | T1204.002 + T1105 | Indicator, and the strongest static claim the module makes |
What the run adds to the reading
Static evidence says what the document is able to do. The detonation says what it did, and the two are reported as different grades of the same claim. The guest is a full Windows virtual machine under a hypervisor, on hardware Malwagon operates, restored from a live memory snapshot of a settled desktop. Nothing is cleaned between runs; the machine is replaced. Its disk is a copy-on-write overlay made for that run alone and destroyed when the run ends, so the document after yours meets the same desktop yours did.
Four recording layers sit outside that machine entirely: packet capture at the hypervisor link, framebuffer capture of the screen as the guest paints it, the guest RAM image read for agentless kernel forensics, and the disk overlay carved host-side once the guest no longer exists. All four are agentless. None of them runs as a process in the guest operating system, so a macro that disables logging or unhooks user-mode instrumentation still leaves the packets, the screen and the memory image intact. Inside the guest, a kernel event trace and the security audit log supply attributed process, file, registry and network activity, and an in-process monitor adds argument-level detail as a second source the report does not rest on alone.
Attribution, and the noise an office application makes
A document run has an attribution problem an executable run does not: the process under observation is a large, chatty application that was already part of the image, and it does an enormous amount of ordinary work simply to open a file. Two things follow from that.
First, the application that opened the document is adopted as the sample's process, and the harness's own launch vector and helper processes are filtered out of the graph. A recipient does not open a document by typing a shell command with the file name as an argument, so leaving that in the tree would mislead the analyst and tell the sample where it is.
Second, part of that ordinary work is condensed out of the behaviour table rather than dropped: what is held back is still counted, and the report states how many rows were held back and why. Autostart locations stay in the table whatever else is condensed, so persistence has nothing to hide behind. Printing every row of it invites an analyst, or a model summarising for one, to narrate persistence that did not happen.
Screen capture is tuned for this module in particular. It starts earlier in the run and reacts to smaller changes on the screen than an executable needs, because a document paints its window within seconds and then sits still, and a cadence built for a long-running process stores an empty desktop and misses the one interesting second. You can also watch the document open live and take the controls, or choose manual mode and open it yourself, on the same run that produces the report.
Everything else that a sample gets, a document gets: behaviour-rule matching over the telemetry, persistence answered from a before-and-after diff of the machine's autostart surfaces, DNS, HTTP and TLS endpoint recovery from the capture, dropped payloads carved and analysed in their own right, an optional memory image with a custom memory rule run against it, and a plain statement of whether the guest ever reached the sample at all.
How a document finding is weighed
The general static chain runs over the same bytes as the document engine and the two views are merged, so a document also receives content typing, cryptographic and fuzzy hashing, entropy, string and obfuscated-string extraction, embedded-secret scanning, signature-rule matching, local antivirus scanning and container carving. Fuzzy hashing is what puts documents into similarity and campaign clustering, which for this class of sample is usually the most useful question you can ask: not what is this file, but what else arrived looking like it. Each part inside the document that is itself a file, the macro storage, an embedded object stream, a carved executable, becomes its own stored artifact with its own hash, rule matches and antivirus verdict.
Alongside the score, the report states the strength of what it has: structural evidence, something the machine watched happen, or agreement across more than one layer. That grade is the line worth reading before the number, because it separates a claim about what the file contains from a claim about what it did. Rules describing a structural defect or a generic technique are barred from naming a malware family, so a malformed but harmless portable document does not arrive attributed to an actor. Where the evidence carries no attribution, the report says so and names the evidence it does have.
| Signature | Severity | ATT&CK |
|---|---|---|
| Document contains VBA macros | Medium | T1059.005 |
| Legacy spreadsheet (XLM) macros, legacy macro execution | High | T1059 |
| VBA stomping (hidden P-code macro) | High | T1564 |
| DDE/DDEAUTO field command execution | High | T1559.002 |
| Remote template injection | High | T1221 |
| External document relationship reference | Medium | T1221 |
| Weaponised CHM (executes a command on open) | High | T1218.001 |
| Launch action in a portable document executes a program | High | T1204.002 |
Triage carries three separate document tags, because they answer three separate questions: does the file contain a macro at all, does that macro run without anyone choosing to run it, and do its primitives read as hostile. A file can be the first without being the second, and the second without being the third, and collapsing them is how a maintenance workbook ends up in the same bucket as a loader.
For OOXML packages the module also produces a disarmed copy: the package is rebuilt without its macro storage parts and without the macro-enabled content-type overrides, the removed parts are recorded by name, and the result is stored as its own artifact with its own SHA-256 and listed among the extracted files.
- Leaves the host: nothing
- The document itself, its macro source, its embedded objects and every carved part stay on the analysis host. There is no upload path for a sample anywhere in this platform, and no exception for a document that looks harmless.
- Leaves the host: derived only
- Hashes and derived indicators are what reach reputation services. A recovered URL is an indicator; the file that carried it is not.
- Reaches the AI layer
- A locally built summary copied through an explicit allowlist: macros present, macro keywords, indicator sentences. The macro source is deliberately excluded, and a test asserts that against the real builder itself, so the guarantee cannot drift away from a description of it.
The same restraint governs the machine-readable surfaces. The API and MCP document fields are the summary only: whether macros are present, a bounded set of macro keywords, and a bounded set of indicator sentences. No macro source, no artifact identifiers, no download links, no bytes. Findings do travel outward in the formats a team already uses, as an ATT&CK Navigator layer, a STIX bundle and a sharing event bundle, all built from derived data.
What this module does not do
- An encrypted document is detected, never opened
- The password field on the form belongs to an archive. There is no document password input anywhere in the product, so the static layer reports the encryption as the finding and the guest is left in front of a prompt nobody answers.
- The disarmed copy has no download route today
- It is built, stored, hashed and listed among the extracted files, but the download endpoints serve extracted files, dropped files and unpacked payloads only. Treat it as a record that the rebuild succeeded and as a list of what was removed. It is not yet a file you can hand back to a user.
- Disarming covers OOXML only
- The portable-document and legacy OLE rebuilds are deferred, and the report names them as deferred, so an empty result cannot read as a clean one. Over the part budget there is no cleaned copy at all.
- The macro source is stored but not displayed
- It is extracted, bounded, kept as an artifact and deliberately withheld from the AI layer. What the report shows is the keyword rows and the indicator sentences. The code itself stays in the artifact store, and this page is not offering you a macro viewer.
- Legacy macro sheets are detected without being reconstructed
- The same applies to a compiled-versus-source disagreement: it is reported, and the compiled stream is left compiled. Both findings point you at the detonation for what the code actually does.
- Embedded objects are described from the container
- Class and packaged file name, with nothing unpacked and nothing executed by this analyser, and the number of objects described is bounded. A recognised exploit-carrier class is a structural fact about what is embedded; only the run can show whether anything fired. Both the OOXML and rich-text object lists currently reach the report as indicator sentences, with no table of class ids, sizes and packaged names.
- Portable-document analysis is keyword and structure based
- It works over the raw file plus a bounded inflation of its compressed streams. There is no full object-graph parse, no execution or emulation of embedded script, no handling of an encrypted portable document, and the URI, launch-target and embedded-name extraction is pattern based and capped. Streams past the inflation budget are not read.
- Compiled help analysis is heuristic
- The container itself is not parsed, so its internal files cannot be enumerated and a command carried outside the shapes the heuristics know is missed. That format also depends on the sample being typed as a document: one arriving typed as a generic file is not opened through its association in the guest.
- Notebook carving is marker driven
- It works to a budget on the number of attachments it will describe, and the report says when that budget was reached. Carved attachments are reported by type and size, and this analyser does not store them as artifacts of their own.
- Markup containers get no format-specific analyser
- Markup, archived-page and publishing formats are accepted and opened in the guest, but their reported format is unclassified and they receive metadata plus the general chain. There is no smuggling analyser for markup in this module.
- A parse that breached its budget leaves a real hole
- The resource-bomb finding is recorded and it is the right finding, but the macro-level answer for that document is missing. Read it as absent evidence, never as evidence of absence. The same is true of a rich-text object parse that had to be abandoned.
- Nothing interacts with the document's own content
- There is no simulated reader: interaction with content the document draws inside its own body is not simulated, and the handling of blocking dialogs is best effort and does not hold on every guest build. Manual mode and the live view are the answer when a document needs a human.
- Free and anonymous runs have no egress at all
- A remote template fetch, a macro downloader or a callback therefore produces no network evidence on those tiers, and that absence is a property of the plan. Read nothing into it about the sample. Those runs are also capped at 120 seconds, so a document that waits out a long pause can finish the run without reaching its payload unless sleep bypass is enabled and the plan allows a longer run.
- One image, one office build
- Behaviour that only triggers on a different version, patch level or locale cannot be reproduced here, and this module has no image matrix. Where a build-dependent question is the whole question, that is another module's job.
- An archive of several files yields one sample
- When an archive holds more than one file, its contents are analysed and the report names what was found. Which member becomes the subject of the detonation is not yours to choose, so submit the document on its own when it is the document you want detonated. A wrong or missing password yields zero-byte stubs, which count as nothing extracted, and the container itself becomes the sample.
- Conservative on purpose
- A weak-but-real technique standing on its own is not treated as an answer; the module waits for the run or another layer to corroborate it before it says more. That is a deliberate trade against a verdict nobody can act on.
Where this module hands off
Neighbouring modules
A document that drops and launches a payload has told you what to look at next. The dropped file is carved from the guest's own disk overlay after the guest is gone and analysed in its own right by the file module, which is also where a submission refused by this form should go when its header turns out to be an executable. A macro that assembles a command line is often easier to read as a command than as a macro, and the command module deobfuscates one locally before detonating it as a script. Where the interesting question is which office build a document triggers on, a OneShot run puts one sample on several images at once and gives the screen back from each.
The boundary underneath all of it does not move for documents. The sample stays on this analysis host, and what travels outward is hashes and derived indicators. The AI layer is fed through a copy allowlist that excludes the macro source. The whole boundary is written out on the security page, and the retention and deletion side of it on the privacy page.