A text file named .html: what our OWASP review found
25 October 2026: a full pass over 443 routes found an upload check that would have let a text file act as a web page. Everything listed here was fixed the same day.
A file can pass a content check and still become dangerous when a server gives it the wrong public identity. That was the most useful lesson from the Net Advice application review on 25 October 2026. A reporting-file upload accepted text content while retaining a client-controlled .html extension on a publicly served disk. A separate avatar case allowed image bytes to keep an HTML filename.
The resulting risk was stored cross-site scripting: a browser could interpret the stored resource as an active page on the application's origin. This article uses that finding to explain how security is implemented and regression-tested in the Flightpack family, with concrete examples from Net Advice, Lumev2 and Sprintly.
Flightpack shares application patterns and feature packages across products. It does not make their installed versions, permissions or security status identical. The examples below distinguish inspected test code, recorded test results and the additional work needed to show that a package fix reached a consuming application.
1. The boundary that the upload check missed
There are several facts about an upload: the filename supplied by the client, its declared content type, the type detected from its bytes, the stored filename, and the response a browser receives when someone opens it. Checking one fact does not automatically make the others safe.
The reviewed reporting upload stored files under a client-derived name and extension in the public storage area. A text body named .html could therefore reach the browser as a web page. The avatar case illustrated a related mismatch: an image with additional HTML content could pass an image check while retaining an HTML extension.
The review followed the upload through storage and retrieval, rather than stopping at validation.
2. The implemented fix has two branches
Reporting documents now pass an extension allow-list that excludes HTML and SVG. The avatar path derives the stored extension from the detected image type. A file detected as PNG is therefore stored with a PNG extension, even when its submitted name claims something else.
Those are related controls with different jobs. A document allow-list defines which kinds of uploaded files the feature accepts. A server-derived image extension prevents a client filename from changing the identity of an accepted image. Neither should be described as a universal malware scanner or a complete replacement for safe download configuration.
Two upload paths, explicit expected outcomes, and a check of the installed dependency.
The five methods in UploadExtensionSecurityTest exercise a PNG with an HTML filename, a mismatched image name, HTML and SVG reporting uploads, and an ordinary text document. The accepted text document matters: tightening the boundary must retain the legitimate feature.
The test helper creates a real uploaded-file object so MIME detection reads the bytes. A fake that guesses the type from a filename could test the test fixture's assumptions instead of the production behaviour.
The review also records an important limitation: two reporting cases were written to skip until the installed library package contained the fix. They passed against package source. A skipped test is not evidence that a consuming deployment has the fixed package. The recorded next step was to update the relevant dependencies and commit the application lock file.
3. Permissions are checked at several boundaries
Flightpack applications use Sanctum for authentication and Spatie Permission for grants. Authentication identifies a caller. An endpoint permission authorises an operation, while a policy or scoped query can restrict which records the caller may act on.
Lumev2 provides a concrete regression example in AdminRoleNameGrantsNothingTest. It creates a role named admin without granting it administrative permissions, then verifies that the name alone does not unlock user management or unrestricted station and geographic access. A vendor holding that role must remain vendor-scoped. An authorised administrator is the positive control.
Sprintly tests a related boundary in CredentialOwnerPermissionTest. A partner credential can read projects while its owner has the required permission. After that permission is revoked, the next call must return 403 with permission_denied. A credential should not preserve an authority its owner no longer has.
These are shared security principles; each application must test its own route and data model.
4. Password changes must change session authority
The Net Advice review found inconsistent token handling after password operations. The corrected password reset deletes all tokens. The authenticated password-change path keeps only the current token, rather than leaving other sessions active. These statements concern the application's token lifecycle; they should not be extended into claims about unrelated identity-provider sessions.
TokenRevocationSecurityTest has three documented outcomes: a reset leaves zero tokens, a password change retains only the current token, and an incorrect current password revokes nothing. The negative case protects against a new denial-of-service bug while fixing the session-retention issue.
5. An outbound request is another trust boundary
A project connection lets the application contact an external system. Validating its hostname only when it is saved is insufficient when DNS can resolve differently later. The documented project-package fix re-checks the destination on each call and pins the connection to a checked address. Redirects were already disabled in that path.
The recorded Net Advice regression refuses a previously saved destination that now resolves internally or no longer resolves, before sending the request. Sprintly's WebhookHostGuardTest separately covers private-address resolution, disallowed HTTP, credentials embedded in a URL, redirects and DNS changes between save and delivery. Similar protections in two features still need separate tests because the actual HTTP clients and call sites can differ.
Saved destination
|
v
Resolve again at call time
|
v
Apply address and transport policy -- refused --> no outbound call
|
v
Connect using the validated destination
|
v
Keep redirects under the implemented policy
6. Archive imports need their own controls
The upload lesson also appears in Sprintly's tracker import, although the danger is different. An archive member can try to escape the intended directory, impersonate a symbolic link, or exhaust resources through excessive size or file count.
TrackerImportUploadTest includes path traversal, absolute paths, symbolic-link entries, oversized archives and excessive entry counts. The implementation described by that suite reads the archive without extracting it. A regression asserts that a rejected path writes nothing outside the intended boundary. This is evidence of an inspected test contract, not a claim that all possible archive formats or parsers have been covered.
7. Audit logging records the change without copying secrets
The Net Advice fixes add named audit entries for role, permission, user-role and settings changes. A useful entry identifies the actor and operation. For settings, it records which keys changed without storing their values, since a configuration value can itself be a credential.
The documented AuditTrailSecurityTest checks that an authorised permission sync names its actor and changes, that settings logs omit values, and that a refused sync does not produce a success-like change record. Logs help reconstruct an event; they do not grant permission or prevent the event by themselves.
8. What we verified, and where
| Project | Concrete evidence reviewed | Limit of the claim |
|---|---|---|
| Net Advice | 25 October review; upload, token-revocation and audit tests; project-connection regression | Historical review and source evidence; package skip and rollout caveats remain relevant |
| Lumev2 | Role-name regression; FP-438 records 1,262 security tests passed with zero failures | A dated run for this application, not proof for every sibling application |
| Sprintly | Credential-owner revocation, tracker-archive and webhook-destination test implementations | Test source inspected for this article; these suites were not newly executed |
The shared starter, mt-lrv, establishes the family relationship. No new starter-suite result is asserted here. A security claim should travel with a package revision, an application configuration and test evidence, rather than with a product family name.
9. Why a complete route ledger missed the upload problem
The Net Advice review covered a recorded inventory of 443 routes, including 244 parameterised routes in the access-control ledger. The route gate reported complete coverage, while manual inspection still found the upload issue. The gate checked a different question: whether access boundaries were accounted for. It did not prove that a permitted operation handled every file safely.
That is why the workflow combines route and permission checks, dependency scans, secret scans, manual data-flow review and targeted regressions. Each provides evidence about a different failure mode. The 15 review entries also included design notes, accepted risks and open items; they were not 15 identical vulnerabilities all resolved at once.
10. A package fix is complete only when consumers can obtain it
This is the propagation workflow; the article does not claim it has completed in every project.
The review recorded dependency updates alongside application fixes. It also retained known exceptions and follow-up work. Passing regression tests does not eliminate dependency maintenance, configuration review or operational verification.
This article focuses on the fixed examples and the shared testing method. It is not an OWASP certification, a fresh penetration test or a declaration that all Flightpack deployments are secure. The stronger practical claim is narrower: specific boundaries have explicit implementations, named regression tests and evidence that can be checked again.
Sources and verification date
Project sources: Net Advice's SECURITY_REVIEW_2026-10-25.md, its upload regression and related test documentation; Lumev2's role-name regression and FP-438 evidence; and Sprintly's credential-owner, tracker-upload and webhook-host tests. The revision reviewed those records on 26 October 2026 without executing the application test suites or probing live vulnerabilities.
For the general defensive principles, see the OWASP File Upload Cheat Sheet on layered upload controls and the OWASP SSRF Prevention Cheat Sheet on destination validation and request restrictions.