The User Uploaded the File. The File Did Not Become the User.
GitHub issue 103689, opened 5 September 2026 against NousResearch's Hermes Agent and still open with no maintainer reply, describes gateway adapters that read a small text attachment's own bytes and write them straight into the same message field carrying the user's own caption, with no marker distinguishing the two once the message reaches the model. This record read the affected source directly. WhatsApp's two adapters confirm the exact mechanism the issue describes. Hermes Agent's own Discord test suite encodes the identical contract as a passing assertion. Slack's and Feishu's adapters, read the same way, show no equivalent injection today, narrower than the issue's own four platform list. A separate, unmerged pull request proposes tracking external provenance for files a command fetches. It does not reach a file a gateway already received.
Event analysed: . This analysis was published on 5 September 2026.
The user's, and that is exactly the gap GitHub issue 103689 names. Opened 5 September 2026 against NousResearch's Hermes Agent by a reporter identified as ifanjuang, carrying no maintainer reply and no linked pull request at the time of this record, the issue states that several of Hermes Agent's gateway adapters take an inbound attachment small enough to decode as UTF-8 text and write its full contents directly into the same message text field that also carries whatever the user typed as a caption, in the approximate form Content of filename, a colon, the file's own bytes, a blank line, then the caption. Nothing in that joined field marks which part the user actually wrote and which part came from whatever authored the file. This record read the affected source directly rather than relying on the issue's own description alone. gateway/platforms/whatsapp_cloud.py and plugins/platforms/whatsapp/adapter.py, both read at commit 006b1beb00d9d25230571d14277aca3d70e5e11f, the exact commit the issue names and, independently confirmed by this record, still the current tip of Hermes Agent's main branch, each carry a document text injection helper that checks a file extension against a small allowed set, enforces a 100 kilobyte size ceiling, decodes the bytes as UTF-8 with lossy replacement for anything that does not decode cleanly, and returns the string Content of filename, a colon, a newline, the decoded text, two newlines, then the existing message body, or the injected text alone when no caption was supplied. Hermes Agent's own test suite states the same contract as a requirement the code must satisfy: tests/gateway/test_discord_document_handling.py asserts that a small .txt or .md attachment's content lands inside the constructed event.text, under the identical Content of filename heading, and separately asserts that the heading's own position in that string is numerically earlier than the caption text's position, meaning the attachment's content is not merely present, it is placed first. This record read plugins/platforms/slack/adapter.py and plugins/platforms/feishu/adapter.py against the same question and found a different current answer for each. Slack's adapter, read directly, builds short file markers such as file colon name and extracts readable text from Slack's own message blocks and legacy attachments, an inbound parsing concern rather than the outbound injection the WhatsApp adapters and the Discord test suite show; no Content of construction, and no path that writes a downloaded file's own bytes into outbound message text, appears in the file this record read. Feishu's adapter, read directly, emits a bare placeholder naming the attachment, such as Attachment colon name, with no function that reads the file's contents into the message at all. Where the underlying issue names Slack and Feishu as carrying the same construction WhatsApp and Discord carry, this record's own direct reading of the current source narrows that claim to what the source actually shows today: confirmed for WhatsApp's two adapters and, through its own test suite's asserted contract, for Discord, and not confirmed for Slack or Feishu as currently written. This record also read tools/read_extract.py, which extracts readable text from formats including Jupyter notebooks, DOCX and XLSX natively and, where an optional dependency is installed, DOC, PPT, PPTX, XLS, ODT, RTF, EPUB and PDF, and gateway/platforms/media_cache.py, which resolves a MIME type and writes a downloaded gateway attachment to a local cached path. Neither function's own return value carries a field recording where the underlying bytes originated, and neither file references the untrusted content wrapper this record's own sibling records already document Hermes Agent applying to output from tools such as a web fetch, a browser action or an MCP call. A file a gateway already downloaded and cached, then later read back through one of these two functions, carries no marker distinguishing it from a file the user actually authored on their own machine. Pull Request 57712, open and unmerged at the time of this record, proposes a related but narrower fix: tracking a filesystem path as externally sourced when a recognized command, a git clone, a gh repo clone, a curl or a wget, fetches it, so a later read_file or terminal call on that same path inherits the classification. That mechanism depends on recognizing the fetching command by name. A gateway attachment is never fetched by any of those commands; the gateway itself already knows, at the moment it downloads the file, that the bytes came from outside the conversation, which is exactly the fact issue 103689 shows getting lost by the time those same bytes reach the model.
Read Hermes Agent's own WhatsApp adapters and the fact that matters is not that the code fails to check anything. It checks a file extension, it checks a size ceiling, it decodes the bytes carefully with a fallback for anything that will not decode cleanly. What it does not check, anywhere in that path, is who wrote the text it is about to hand the model as though the user had typed it.
What issue 103689 says, and what this record independently verified
GitHub issue 103689, titled Gateway attachment content loses provenance and is injected as user authority text, was opened against NousResearch's Hermes Agent on 5 September 2026 by a reporter identified as ifanjuang. At the time of this record it carries no maintainer reply, no comments, and no linked pull request. It carries the labels type security, comp agent, comp gateway, tool file and P3. This record treats an open issue with no maintainer confirmation as exactly that: a credible, independently verifiable description of source level behavior, not a maintainer acknowledged defect and not evidence of any confirmed CVE.
The issue states its own reviewed commit as 006b1beb00d9d25230571d14277aca3d70e5e11f. This record independently confirmed two things about that commit. First, it is not a commit that touches attachment handling itself; its own message describes an unrelated test addition for how file tools report a dead execution environment. Second, and more useful to this record, it is, at the time of this record's own check, still the current tip of Hermes Agent's main branch, meaning the issue's own review and this record's independent reading are looking at the identical, current state of the affected adapters rather than a state main has since moved past.
The confirmed mechanism, read directly in both WhatsApp adapters
Hermes Agent ships two separate WhatsApp integrations, gateway/platforms/whatsapp_cloud.py, built against Meta's own WhatsApp Cloud API, and plugins/platforms/whatsapp/adapter.py, a separate plugin adapter. This record read both directly. Each carries its own document text injection helper, differing only in cosmetic detail, such as how the display filename is derived when a media URL carries no clean name of its own. Both share the same governing constants: a fixed set of file extensions eligible for text injection, and a fixed maximum byte ceiling of 100 kilobytes above which a file is cached for later reference rather than injected. Both decode the file's bytes as UTF-8 with errors replaced rather than raised, so a file that is not cleanly UTF-8 encoded still produces text rather than failing outright. Both then build the same string shape: Content of, the filename, a colon, a newline, the decoded text, two newlines, and the message's existing caption, or the injected text alone when the user supplied no caption. That combined string becomes the message text the rest of Hermes Agent's pipeline treats as what the user said.
Nothing in that string, and nothing in either function's own return type, marks a boundary between the two components. A downstream reader sees one field, one role, one apparent author. The attachment's own words and the user's own words are typographically distinguishable, a human reading the raw string can see the Content of heading, but nothing in the data structure itself tells a model, or any later processing step, that the words after the heading were not typed by the person the message is attributed to.
Discord's own test suite states the same contract as a requirement
This record could not fully read plugins/platforms/discord/adapter.py directly; the file exceeded what this session's page fetch tool could return in one pass. What this record read instead, in full, is tests/gateway/test_discord_document_handling.py, which is stronger evidence for exactly the question this record is asking, since a passing test is a claim the maintainers themselves have encoded about what the code must do. The test file asserts, among other cases, that a small .txt attachment's content is inserted into the constructed event.text field under a heading reading Content of notes.txt, that a small .md attachment's content is inserted the same way under Content of readme.md, that a file exceeding the 100 kilobyte ceiling is cached rather than injected, and that multiple small text attachments in one message are each injected in order. One assertion in particular states the ordering directly: the heading's own index inside event.text is checked to be numerically smaller than the index of a phrase drawn from the user's own caption, meaning the suite does not merely tolerate the attachment's content appearing somewhere in the field, it requires that content to appear first, ahead of whatever the user actually typed.
Slack and Feishu, read directly, do not show the same construction today
The underlying issue names Discord, Slack, WhatsApp and Feishu together as carrying the same pattern. This record read plugins/platforms/slack/adapter.py and plugins/platforms/feishu/adapter.py directly rather than accepting that list as given, consistent with this record's own rule that a platform is named only when its current source supports the claim.
Slack's adapter builds short file markers, strings such as file colon name or image colon name, for use elsewhere in the pipeline, and separately extracts readable text from Slack's own legacy message attachments and Block Kit structures. Both are inbound parsing of what Slack itself sent Hermes Agent about a message, not the outbound construction the WhatsApp adapters and Discord's own test suite show: nothing in the file this record read takes a downloaded attachment's own file contents and writes them into outbound message text alongside a caption. Feishu's adapter, read directly, is narrower still: it emits a bare placeholder naming the attachment, such as Attachment colon name, with no function anywhere in the file that reads a document's bytes at all.
Caching and extraction: the same gap, one layer further from the gateway
gateway/platforms/media_cache.py resolves a MIME type for a downloaded gateway attachment and delegates to platform specific caching primitives that write the bytes to a local path, returning that path with no field describing where the bytes came from. tools/read_extract.py extracts readable text from a range of document formats, Jupyter notebooks, DOCX and XLSX natively, and, where an optional dependency is installed, DOC, DOCM, PPT, PPTX, XLS, XLSM, ODT, ODS, RTF, EPUB and PDF, through two functions, one taking a file path and one taking raw bytes with a filename, neither of which accepts or returns any origin, source or trust field. Nothing in either file references the untrusted content wrapper this record's own sibling records already document Hermes Agent applying to output from a web fetch, a browser action or an MCP tool call. A file a gateway already downloaded from an inbound attachment, cached under one of these paths, and later read back through read_extract carries no marker anywhere in that chain distinguishing it from a file the user wrote themselves on their own machine and handed to a local tool directly.
What Pull Request 57712 does, and why it does not reach this gap
Pull Request 57712, open and unmerged at the time of this record, proposes a real and separately useful fix for a related issue, 57710: read_file and terminal, unlike tools such as a web fetch, are not globally wrapped as untrusted, because an ordinary local project file should not carry that overhead by default. The proposed fix tracks filesystem provenance by recognizing when a small set of named commands, git clone, gh repo clone, curl with an output flag, or wget, fetch a path, storing that path as an untrusted root for the session, and checking later read_file or terminal calls against it.
That mechanism is command detection: it knows a path is external because it recognizes the command that produced it. A gateway attachment never passes through any of those commands. The gateway's own download and cache step already knows, at the exact moment it writes the file, that the bytes arrived from outside the conversation, a fact command detection has no way to discover after the fact, because there is no clone, no curl, no wget in the chain for it to recognize. This record does not treat issue 103689 as a duplicate of 57712, and does not treat 57712, unmerged as it is, as a fix for 103689's own gap. The two describe the same underlying architecture, provenance that should travel with content rather than being inferred from the tool that last touched it, discovered from two different directions: 57712 from a command a session observed, 103689 from an ingress point that already knows.
The Authority Provenance ledger
Transport principal. The user who sent, forwarded or uploaded the message through WhatsApp, Discord, Slack or Feishu, whichever gateway received it. This is the principal Hermes Agent's own message role, user, actually names.
Content principal. Whoever authored the attachment's own text, established by nothing in the mechanism this record read. It may be the same person as the transport principal, a colleague who wrote the document, a website the user copied text from, or, in the adversarial case the underlying issue exists to name, a party who deliberately wrote instructions into a file expecting an agent to read them as the user's own words.
Claimed authority. None is claimed in the sense this desk's own claimed authorization weakness describes; nobody asserts I am authorized inside the attachment's text. The gap here is structural rather than deceptive: the architecture itself, not any statement inside the file, assigns the content the transport principal's authority, by the simple fact of where it is written.
Mechanism. A document text injection helper, confirmed identically in gateway/platforms/whatsapp_cloud.py and plugins/platforms/whatsapp/adapter.py and confirmed by asserted test contract in Discord's own test suite, that decodes an attachment under 100 kilobytes as UTF-8 and writes it, headed by a Content of filename marker, ahead of the user's own caption inside one message text field.
Boundary marking. A human readable heading, Content of filename, present in every confirmed case. Absent: any machine readable field, structured separation, or role distinct from the message's own user role that would let a downstream reader treat the two components differently.
Persistence. gateway/platforms/media_cache.py caches a downloaded attachment to a local path with no origin field in its return value. tools/read_extract.py extracts text from a cached or supplied document with no origin field accepted or returned. A file's external origin, known for certain at the gateway's own download step, is not represented anywhere past that step.
Adjacent, narrower mechanism. Pull Request 57712, open and unmerged, tracks external provenance for a path a recognized fetch command produced, checked later against read_file and terminal calls. It does not, and by its own command detection design cannot, reach a path a gateway already wrote from an inbound attachment.
Challenge authority. None found in the source this record read. No scanner, classifier or untrusted content wrapper sits between the attachment text and the message field it is written into.
Maintainer response. None at the time of this record. The issue carries no reply, no linked pull request, and a P3 label, which this record reads as a routing priority, not as a maintainer judgment that the underlying architecture question is minor.
Consequential execution. Not established, and not claimed by this record. Nothing in issue 103689 or in the source this record read demonstrates a specific downstream tool call executed as a result of attachment content read this way; the issue, and this record following it, concerns context and instruction authority arriving inside the model's input, not a documented instance of that authority being acted on.
Exploitation. Unknown. No source available to this record states this mechanism has been exploited against a production Hermes Agent deployment.
Provenance evidence quality. Strong for the mechanism itself in WhatsApp's two adapters and in Discord's own asserted test contract, each read directly against the exact commit the underlying issue names, independently confirmed as the current tip of the main branch. Weaker, and stated as a limitation rather than rounded up, for plugins/platforms/discord/adapter.py itself, which exceeded this session's own page fetch tool and was not read in full; the test suite's own asserted contract is treated as strong secondary evidence for what that file does, not as a substitute for reading it directly. Narrowing, not strengthening, for Slack and Feishu: this record's own direct reading found no equivalent construction in either, against the underlying issue's own broader claim.
Where this sits against this desk's own existing canonicals
This desk's own record on claimed authorization already tracks a related but distinct failure inside a different deployment of the same product: Cybernews's reporting on an affiliate of the Gentlemen ransomware operation describes an operator getting Hermes Agent itself to compromise real organizations by presenting the work as a fabricated training exercise, a claim made in words that Hermes Agent accepted with nothing external to the claim confirming it. This record's own gap is upstream of that one and does not require any claim at all. Nobody has to assert an attachment is trustworthy for its content to inherit the sender's authority under the mechanism this record describes; the architecture does that on its own, before any claim is made or evaluated.
This desk's wider argument that execution authority has to be evaluated at the moment of the consequential action, not assumed from an earlier admission, extends here to a moment earlier still: the moment a composite message is assembled, before any tool call, any objective, and any claim of authority ever enters the picture. A system that gets every later authorization check right still inherits a false answer to a question it never separately asked: who actually wrote the words this message is about to hand the model as its own user's instruction.
This record also keeps a separate Moona Intelligence registry entry distinct rather than merged with it. AEV 2026 0018 documents a different, unrelated Hermes Agent mechanism, an automatic git status call executing a repository's own core.fsmonitor setting during routine context gathering, confirmed by Manifold Security and assigned CVE 2026 71963. That is an execution authority gap at a different point in Hermes Agent's own lifecycle, repository context gathering rather than gateway message ingestion, and this record does not treat the two as the same weakness, the same evidence, or the same fix.
What this record does not establish
This record does not claim issue 103689 has been confirmed by a Hermes Agent maintainer, that a CVE has been assigned to this mechanism, that Slack or Feishu carry an equivalent construction anywhere this record did not read, that Pull Request 57712 fixes gateway attachment ingestion, that Pull Request 57712 has merged, or that any specific downstream tool call has been executed as a result of an attachment's content being read this way. Where the evidence available to this record does not establish a fact, this record states it as unknown rather than inferring it from the pattern this weakness class already shows elsewhere.
Sources
This analysis interprets third-party reporting, research and announcements. Moona is not the original reporter of the underlying events.
