Background
While building Nexus, my own link-in-bio platform, I implemented a feature that allowed users to customize their page backgrounds.
At first, the feature seemed harmless. Users could select colors, gradients, and other visual styles to personalize their profile pages.
The background field accepted values like:
linear-gradient(to right, #ff6b6b, #4ecdc4)
Everything worked as expected.
Then I started asking myself a question that every developer should occasionally ask:
What happens if a user enters something I didn't originally intend?
Instead of supplying a color or gradient, I tried supplying a CSS url() value.
url("https://example.com/image.png")
To my surprise, it worked.
The browser happily fetched the external resource and rendered it as the page background.
That immediately changed how I looked at the feature.
Following the Trail
At this point, I wasn't interested in making the page look different.
I wanted to understand exactly how much control a user had over the generated CSS.
After additional testing, it became clear that the application was taking user input and inserting it directly into a CSS property without restricting the allowed values.
In other words, users weren't choosing from predefined background options.
They were effectively supplying raw CSS values.
The application wasn't vulnerable to JavaScript execution through this input. Modern browsers and security protections prevented obvious attempts such as:
javascript:alert(1)
However, the ability to inject arbitrary CSS values still opened interesting possibilities.
Before reporting anything, I wanted to understand the boundaries of the issue.
Some of the things I tested included:
- External image loading through url()
- Different CSS functions and nested values
- Attempts to break out of the property context
- JavaScript URI schemes
- Data URIs
- Remote asset loading from domains I controlled
- Browser behavior across different rendering scenarios
Most of the dangerous payloads were blocked by modern browser protections, but unrestricted CSS values were still being accepted and rendered.
My First Experience With inapp.app
While exploring different link-in-bio products, I came across inapp.app.
The platform had a clean interface and offered extensive customization options, including profile backgrounds.
As someone actively building Nexus, I naturally became curious about how these customization features were implemented.
While experimenting with the background settings, I noticed behavior that looked very similar to what I had previously encountered during development.
That curiosity led me to begin testing how user-supplied values were handled.
Why CSS Injection Matters
Many developers hear "CSS" and immediately assume the impact must be low.
But unrestricted CSS can introduce several security concerns:
- Loading external resources controlled by an attacker
- User tracking through externally hosted assets
- UI manipulation
- Visual spoofing
- Deceptive overlays
- Clickjacking-style interfaces
- Unexpected requests originating from victim browsers
Even when JavaScript execution is impossible, allowing arbitrary CSS can create opportunities for abuse.
Security issues don't always start with code execution.
Sometimes they start with trust.
Testing a Real Platform
After observing this behavior in Nexus, I became curious whether similar implementations existed elsewhere.
While exploring inapp.app, I noticed a very similar pattern.
The platform allowed users to customize backgrounds, and after some testing I discovered that custom CSS values appeared to be passed directly into the generated styles.
Again, JavaScript execution was not possible during my testing.
However, external resources could still be referenced through CSS functions such as url() at first I tried it with url only and not to my surprise it worked the app requires you to have a premium subscription to do that and yet I can do it for free.
Next I went on to test what other things I can do like can I leak into other components to modify it I tried by positioning things and letting it go on, I eneded up wit hthis result.
Then I completely leaked into the page's CSS and broke the page and I made it stay that way so I can show the proof of concept I was thinking
I opened up the developer tools panel and yet it confirmed that my input was indeed not passed though any checks just embeded into the page without any sort of validation hence I was able to produce the above effects.
This demonstrated an important lesson:
Features that seem purely cosmetic often become security-relevant when user input reaches the browser without strict validation.
Before reporting the issue, I documented my findings carefully and collected screenshots demonstrating the behavior. I created several accounts and tested the things and yes indeed it was a app level bug and not a user level.
I wanted to ensure the report was clear, reproducible, and focused on helping the team understand the root cause rather than simply pointing out a bug.
Reporting the Issue
Once I had gathered enough information, I responsibly reported the issue on X because I didn't find any way to contact the owners of the application. Here is the orignal X post I made reguarding this issue.
During the process, I shared screenshots and technical details showing exactly how the behavior could be reproduced.
The goal wasn't to create drama or chase attention it was simply to help improve the security of a product that many creators use.
Resolution
The best part of the experience wasn't finding the issue. It was seeing it get fixed. I felt like I made an impact just like my experience with contributing to open source
After the report was reviewed, the underlying behavior was addressed and the platform implemented changes to prevent arbitrary CSS values from being accepted in the same way.
Seeing a report move from discovery to remediation is always satisfying because it means the research had a real-world impact.
A Personal Message From Hitesh
One moment that stood out to me was receiving a personal DM from Hitesh himself regarding the report.
As someone who has learned a lot from the developer community and followed the work being done around products like inapp.app, that message meant a lot.
It reinforced something I've come to appreciate about good engineering teams:
They take reports seriously, communicate openly, and focus on fixing problems rather than ignoring them.
The Fix
The safest solution is not to sanitize arbitrary CSS.
The safest solution is to avoid accepting arbitrary CSS entirely.
Instead:
- Restrict backgrounds to predefined colors.
- Restrict gradients to validated patterns.
- Reject url() values.
- Use allowlists rather than blocklists.
- Treat every style-related input as untrusted data.
In Nexus, I eventually moved toward validating the specific formats I intended to support rather than accepting any CSS value the browser would understand. I resolved to supporting css values with specific validations and not other things or leaking out css values.
Final Thoughts
I didn't discover this issue while hunting for vulnerabilities. I discovered it while building a product.
That's one of the most valuable lessons I've learned as a developer: security research often starts with curiosity.
The moment you stop asking "Does it work?" and start asking "What else can it do?" you begin seeing applications from a completely different perspective.
And sometimes, a simple background color picker becomes a security lesson.
More importantly, this experience reminded me that responsible disclosure works.
A curious observation became a technical investigation, a technical investigation became a report, and that report ultimately led to a fix.
For me, that's what security research is really about: understanding systems deeply enough to make them better.







Top comments (0)