
Akaunting 3.3.0 - Stored XSS in invoice edit form through imported status
7
High
Detected by

Fluid Attacks AI SAST Scanner
Disclosed by
Miguel Gómez
Abstract
Fluid Attacks' AI SAST detected CVE-2026-118855, a high-severity stored XSS vulnerability (CWE-79) in Akaunting 3.3.0. A Manager can execute code in an administrator's browser through an imported invoice status. It has a CVSS v4.0 base score of 7.0. This advisory includes a proof of concept.
Summary
Full name
Akaunting 3.3.0 - Stored cross-site scripting through XLSX-imported document status rendered in the invoice edit hidden-input attribute
Code name
State
Public
Release date
Affected product
Akaunting
Vendor
Akaunting
Affected version(s)
3.3.0
Vulnerability name
Stored cross-site scripting (XSS)
Vulnerability type
Remotely exploitable
Yes
CVSS v4.0 vector string
CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:A/VC:H/VI:H/VA:N/SC:L/SI:L/SA:N
CVSS v4.0 base score
7.0
Exploit available
Yes
CVE ID(s)
CWE
CAPEC
Description
Akaunting 3.3.0 contains a stored cross-site scripting vulnerability in its invoice-import workflow. An authenticated Manager can import an XLSX document whose status cell contains HTML. When a second user opens the resulting invoice's edit page, Akaunting places that stored value into a hidden input using raw Blade output, allowing attacker-controlled markup to be parsed in the second user's authenticated browser session.
Vulnerability
Root cause
The status field is a document-state value, not a rich-text field. Akaunting's own commented validation rule enumerates the expected states (draft, paid, partial, sent, received, viewed, and cancelled), but the active rule is only required|string. Consequently, the spreadsheet importer accepts and persists arbitrary HTML in documents.status.
The edit form then renders this untrusted value into a double-quoted HTML attribute with Blade's raw {!! !!} syntax. Attribute serialization is not performed, so a value that closes the value attribute can add a new element with an event handler. This is not intended spreadsheet-import behavior: invoice status is an internal workflow state and the UI renders it as a hidden form value.
Source-to-sink path
A user with
create-sales-invoicessubmits an XLSX file toPOST /{company}/sales/invoices/import. The route dispatchesSales\\Invoices::import().app/Imports/Sales/Invoices/Invoices.phpmaps theinvoicesworksheet toapp/Imports/Sales/Invoices/Sheets/Invoices.php.Sheets\\Invoices::map()callsApp\\Abstracts\\Import::map(), which only normalizes invalid UTF-8; it does not encode HTML. Itsmodel()method returnsnew Document($row).App\\Abstracts\\Import::withValidator()instantiatesApp\\Http\\Requests\\Document\\Documentfor each import row and applies its rules. The active validation rule atapp/Http/Requests/Document/Document.php:52isrequired|stringforstatus; the restrictivein:rule immediately above it is commented out.Document::$fillableincludesstatus, so the supplied value is stored.Sales\\Invoices::edit()passes the document to the edit view.App\\View\\Components\\Documents\\Form\\Content::render()reads$this->document->status, andresources/views/components/documents/form/content.blade.phppasses it to<x-form.input.hidden name="status">.resources/views/components/form/input/hidden.blade.php:4rendersvalue="{!! $value !!}". A stored value beginning with an attribute-closing sequence and an HTML element is therefore interpreted by the browser as markup when the edit form loads.
Impact
An attacker can execute JavaScript in the origin and authenticated session of a user who opens the malicious invoice edit form. The script can read data exposed to that session and issue same-origin requests permitted by that session.
PoC
Preconditions
Use Akaunting 3.3.0 with a test database and test company. Create one account with the predefined Manager role and a separate Administrator account in that company. The Manager has sales-invoices: c,r,u,d, but not user-management permission. Keep invitation delivery local or disabled and use separate browser profiles for the two accounts.
Step-by-step
Install the released
3.3.0tag locally, complete installation, create company ID1, and confirm the version.As the initial Administrator, create a user with the predefined Manager role and sign in through a separate browser profile.
In an empty local directory, run the following Python generator. It creates
documentstatus-safe-marker.xlsxwith the six worksheet names declared by Akaunting's importer and setsinvoices.statusto a payload that only displays anXSSalert. Runsha256sumafter generation and retain the output with the test notes.As the Manager, open Importer → Import Invoices and upload
documentstatus-safe-marker.xlsx. Confirm Invoices imported! and identify the new invoice.In the Administrator browser profile, open
GET /{company}/sales/invoices/{invoice}/edit, replacing{invoice}with the imported invoice ID.The browser displays an XSS alert. Close it without saving the invoice; this confirms that the imported value reached the raw hidden-input sink.
Evidence of Exploitation
Video of exploitation:
Static evidence:

Our security policy
We have reserved the ID CVE-2026-118855 to refer to this issue from now on.
System information
Product: Akaunting
Version: 3.3.0
Operating System: Any supported Akaunting deployment
References
GitHub Repository: https://github.com/akaunting/akaunting
Security: https://github.com/akaunting/akaunting/blob/master/SECURITY.md
Release: https://github.com/akaunting/akaunting/releases#release-3.3.0
Solution and workaround
Solution
Akaunting should restore an allowlist for document statuses, including in spreadsheet-import validation, and reject values outside supported state identifiers. It should also replace raw Blade output in the hidden-input component with context-appropriate escaped output, for example value="{{ $value }}". The output encoding change is necessary even if input validation is restored, because the shared component may receive untrusted values from other flows.
Workaround and temporary mitigation
Until a fix is available, restrict invoice-import permission to trusted users and prevent privileged users from opening edit pages for newly imported invoices from untrusted sources. Administrators can also inspect and remove document rows whose status is not one of the supported state identifiers. These measures reduce exposure but do not replace a code fix.
Credits
The vulnerability was detected by AI SAST Scanner and disclosed by Miguel Gómez from Fluid Attacks' Offensive Team.
Timeline
Vulnerability discovered
Vendor contacted
Public disclosure
Does your application use this vulnerable software?
During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.














