@@ -58,4 +58,23 @@ public async Task<IActionResult> Start(int id, CancellationToken ct)
58 await _db.SaveChangesAsync(ct);
59 return NoContent();
60 }
61+
62+ [HttpGet("export")]
63+ public async Task<IActionResult> Export([FromQuery] string customer, CancellationToken ct)
The new Export action has no [Authorize] attribute, while every other action in this controller does. Every action needs its own permission check, so this one is open to anyone, including people who are not logged in, and they can download work order data.
Suggested fix: Add [Authorize(Policy = "WorkOrders.Read")] above the Export method.
64+ {
65+ var rows = await _db.WorkOrders
66+ .IgnoreQueryFilters()
67+ .Where(w => w.CustomerName.Contains(customer))
IgnoreQueryFilters() switches off the automatic filter that keeps each company to its own data, and the team rules forbid it outside the Admin folder. The customer-name match is also a partial match ('Contains'). Together, a search for a short or common name returns work orders from every tenant, so one customer's prices and descriptions leak to another company.
Suggested fix: Remove .IgnoreQueryFilters(). Filter by a customer id (or an exact name match) rather than Contains, and return 400 if the customer parameter is missing.
68+ .OrderBy(w => w.ScheduledFor)
69+ .ToListAsync(ct);
70+
71+ var csv = "Id,Customer,Description,Scheduled,Total\n";
72+ foreach (var row in rows)
73+ {
74+ csv += row.Id + "," + row.CustomerName + "," + row.Description + "," + row.ScheduledFor + "," + row.QuotedTotal + "\n";
Values are joined with commas but never quoted. Any customer name or description containing a comma, quote, or line break will split into extra columns or rows, so the spreadsheet comes out scrambled. Work order descriptions often contain commas. A value starting with '=' would also run as a formula in Excel (CSV injection). ScheduledFor and QuotedTotal are formatted using the server's regional settings, which may use commas as decimal separators.
Suggested fix: Use a CSV library (e.g. CsvHelper), or wrap every field in double quotes with inner quotes doubled. Prefix values starting with =, +, - or @ with a single quote. Format dates and numbers with CultureInfo.InvariantCulture (e.g. ISO 8601 dates).
75+ }
76+
77+ System.IO.File.WriteAllText("C:\\exports\\workorders-" + customer + ".csv", csv);
The file name is built from the 'customer' value sent in the request. A value like '..\..\Windows\something' writes outside the exports folder, so a caller can overwrite files on the server. The hard-coded C:\exports path also breaks the rule against file paths in code, and it will throw an error (failing the whole export) on any server where that folder doesn't exist, such as Linux or a container.
Suggested fix: Drop the server-side copy, or write to a location from configuration using a generated, safe file name (for example tenant id + timestamp + GUID), never the raw query value. Handle a write failure without failing the download.
78+ return File(System.Text.Encoding.UTF8.GetBytes(csv), "text/csv", "work-orders.csv");
79+ }
80 }