# Securing Laravel > The essential security resource for Laravel developers. Public Ghost content for AI and LLM tooling. This file includes a bounded export of public pages first, then recent public posts. Append `.md` to any post or page URL to get the content in Markdown (for example, `/example-post.md`). ## Pages ### Welcome to Securing Laravel! URL: https://securinglaravel.com/about/ Last updated: 2024-05-05T12:20:42.000Z Hey there, I'm [**Stephen Rees-Carter**](https://pinkary.com/@valorin?ref=securinglaravel.com), the creator of **Securing Laravel**. It's awesome to meet you and I want to welcome you to our community! I started **Securing Laravel** (then called *Laravel Security In Depth*) back in August 2021 as a way to share my security knowledge with the Laravel community, alongside my [Conference Talks](https://pinkary.com/@valorin?utm%5Fcampaign=nav&utm%5Fsource=securinglaravel.com). It has since grown to over 3,500 subscribers (as of April 2024), and is showing no signs of slowing down. In addition to writing **Securing Laravel** each week, I now work full time doing [Laravel Security Audits and Penetration Tests](https://stephenreescarter.net/laravel-security-audits-and-pentesting/?utm%5Fcampaign=about&utm%5Fsource=securinglaravel.com), which gives me a unique insight into how different Laravel developers are using the framework. ### What is Securing Laravel? Each month we dive into Laravel security concepts through code examples, practical security knowledge, hacking techniques, and interactive challenges, covering the essential topics you need to know to keep your apps secure. In the weeks between, we have quick security tips that cover the simpler topics, configuration options, tricks, updates, and anything else security related you need to be aware of. ### Who is Securing Laravel for? **Securing Laravel** is written for Laravel developers of all skill levels. Every concept we cover is fully explained for security newbies *(with no pesky unexplained jargon!)*, but we also dive deep for those looking to learn even more. ### Why Should I Become a Paid Subscriber? Paid Subscribers get full access to all past **In Depth** articles, as well as new In Depth articles published each month, while free subscribers will only have access to the weekly **Security Tips**. Since I am an independent security consultant, paid subscribers directly support **Securing Laravel** by funding the time I spend each week researching and writing security tips and in depth articles. Your support also allows me to work with the Laravel community, making security upgrades to the framework itself, and reviewing and supporting third party plugins with security reviews. If you haven't signed up yet, please consider subscribing: [Subscribe Now!](#/portal/signup/free) Please feel free to reach out to me at [stephen@securinglaravel.com](mailto:stephen@securinglaravel.com) if you have any questions or feedback, and you can find me on various socials at [https://pinkary.com/@valorin](https://pinkary.com/@valorin?ref=securinglaravel.com). Thanks, Stephen P.s. If you're still unsure about subscribing or upgrading to a paid subscription, here's what others have said about Securing Laravel: 💬 **"Stephen is one of the preeminent voices in the Laravel security community." \~Ian Landsman, "The Godfather of Laravel"* 🗨️ **"It’s well-written, easy to understand, and makes my apps so much better!" \~Aaron Bushnell* 🗯️ **"Thanks, Steven. This is really lovely reading the level of detail, the amount of outside references, and the good natured-ness of your writing. Really, really helpful in making it clear and getting it across both how simple it is to exploit and how simple it is to fix." \~Jason Stewart* [Jim Hull (@jimhull.blog)You are doing some of the most important work out there for Laravel. Thank you!🙏🏼 C![](https://static.ghost.org/v5.0.0/images/link-icon.svg)Bluesky Social](https://bsky.app/profile/jimhull.blog/post/3koixurppi42x?ref=securinglaravel.com) > [@valorin](https://twitter.com/valorin?ref%5Fsrc=twsrc%5Etfw&ref=securinglaravel.com) is an absolute champ. Top-notch security tips for practically every dev > > — Alex Six (@alexandersix\_) [February 7, 2024](https://twitter.com/alexandersix%5F/status/1755093119261913275?ref%5Fsrc=twsrc%5Etfw&ref=securinglaravel.com) > Security is the number 1 priority when deploying to production 🛡️[@valorin](https://twitter.com/valorin?ref%5Fsrc=twsrc%5Etfw&ref=securinglaravel.com) newsletter is full of useful safety measures that can be easily overlooked during the development stage of a [@laravelphp](https://twitter.com/laravelphp?ref%5Fsrc=twsrc%5Etfw&ref=securinglaravel.com) app ⚠️ > > Highly recommended 🏅 [https://t.co/lX9Oz8AdBC](https://t.co/lX9Oz8AdBC?ref=securinglaravel.com) > > — Andrea Marco Sartori (@cerbero90) [January 18, 2024](https://twitter.com/cerbero90/status/1747989343644643830?ref%5Fsrc=twsrc%5Etfw&ref=securinglaravel.com) > If you’re a Laravel dev and you haven’t yet signed up for the excellent Securing Laravel newsletter from [@valorin](https://twitter.com/valorin?ref%5Fsrc=twsrc%5Etfw&ref=securinglaravel.com), you’re missing out. Such a great variety of topics, but it also goes deep at times to really help you understand something better. [https://t.co/WoPVmwySiD](https://t.co/WoPVmwySiD?ref=securinglaravel.com) > > — Joel Clermont (@jclermont) [January 16, 2024](https://twitter.com/jclermont/status/1747276298387402994?ref%5Fsrc=twsrc%5Etfw&ref=securinglaravel.com) # Advertising, Promotions, Sponsorship **Securing Laravel** is a community funded publication, 100% supported by and focused on subscribers. No paid advertising or sponsorships will be accepted, and unsolicited sales emails will typically be ignored. You will not see ads or sponsors featured on **Securing Laravel**. All emails are written and edited by **Stephen Rees-Carter**, and any mention or promotion of third-party packages, products, and services, is carefully considered. Paid products and services are only recommended with Stephen's personal experience, and any financial interests (such as affiliate links) will be fully disclosed. *In short, subscribers need to trust the recommendations and information shared in *Securing Laravel*, and trust cannot be gained when there is an undisclosed financial interest at state.* ### Start a 7 Day Trial URL: https://securinglaravel.com/start-a-7-day-trial/ Last updated: 2024-09-12T02:19:32.000Z Hey there! Due to limitations with this platform, I can't send you to a shiny sales page that offers 7-day trials and lets you pick which billing cycle you'd prefer when the trial ends. So you're stuck with two boring buttons... [Start a 7-day trial on monthly billing](https://securinglaravel.com/7-day-trial-monthly) [Start a 7-day trial on yearly billing](https://securinglaravel.com/7-day-trial-yearly) By using the above buttons, you'll get full access to a premium subscription for 7-days before being charged anything, and you can cancel at any time. Also, if you miss the date and get charged, shoot me an email within 7 days of the charge and I'll issue a full refund, no questions asked. ### Sponsor Securing Laravel URL: https://securinglaravel.com/sponsor/ Last updated: 2025-05-21T12:08:17.000Z **Want to get your brand in front of thousands of Laravel developers every week, while supporting security education across the Laravel ecosystem?** Sponsoring *Securing Laravel* is the perfect way to do both. Every week\* I publish a new [**Laravel Security Tip**](https://securinglaravel.com/tag/tips/), covering a wide range of security topics to help Laravel developers stay secure. I cover recent vulnerabilities, common attacks, secure coding patterns, package reviews, and more. Each Security Tip either teaches something new or offers a timely refresher on an easily forgotten topic. Every article is - Published on [securinglaravel.com](https://securinglaravel.com/) (\~10,000 views per month) - Emailed to **4,000+ subscribers** (56% engagement rate) - Shared across [Twitter](https://x.com/valorin?ref=securinglaravel.com), [Bluesky](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com), [Mastodon](https://phpc.social/@valorin?ref=securinglaravel.com), [LinkedIn](https://www.linkedin.com/in/stephen-rees-carter/?ref=securinglaravel.com), and [Threads](https://www.threads.com/@valorinsrc?ref=securinglaravel.com). *\*Note: The schedule follows a 4-week cadence - 3 weeks of Security Tips, followed by a paid In Depth article.* ## Why Sponsorship? *Securing Laravel* is supported by paid subscribers (who get access to the paid [In Depth](https://securinglaravel.com/tag/in-depth/) articles), but as an independent security consultant, the paid mailing list model has limited scope - it only just covers the time I spend creating these articles. Bringing sponsors onboard will help me: - Review more third-party packages - Dive deeper into Laravel's internals for obscure vulnerabilities - Expand the scope of what Securing Laravel can offer the community ## What You Get as a Sponsor There are two levels of sponsorship available: ### Premium - Sponsored Box - A **Sponsored box** at the bottom of the weekly Security Tip (emailed + published on the site) - Your sponsor box stays on the article for **at least 6 months** - **Exclusive placement** \- only **one sponsor per week** - A **social media shoutout** (e.g., *“This week, Securing Laravel is sponsored by XYZ”*) with a link to your site or socials - Your sponsor box added to up to **5 old Security Tips** that are promoted on socials during your sponsored week **Sponsor box content requirements** - 1-2 sentences of text up to 255 characters max. - Custom button text + link - Example: SPONSORED This is an example sponsor box. You specify this text and define the button & link. [Example Button! ](https://securinglaravel.com/sponsor) ### General - Sponsor Logo - **Your Logo** at the bottom of the weekly Security Tip (emailed + published on the site) - Your logo stays on the article for **at least 6 months** - **Shared placement** \- multiple logos may be included on each article - Your logo added to up to **5 old Security Tips** that are promoted on socials during your sponsored week **Sponsor logo content requirements** - Single image, max 300px (w) x 100px (h) - Image Alt text - Custom link - Example: *Securing Laravel is sponsored by:* [![Securing Laravel - The essential security resource for Laravel developers.](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2025/05/Securing-Laravel-Banner-1.png)](https://securinglaravel.com/) --- ## Pricing ### Sponsored Box - $750 USD per Security Tip (i.e. per week) - $2,100 USD for 3x Security Tips - $4,000 USD for 6x Security Tips ### Sponsor Logo - $750 USD per month (3x Security Tips) ## Interested? Email me at [stephen@securinglaravel.com](mailto:stephen@securinglaravel.com) to check availability and book your sponsorship. --- ## Notes - I only accept sponsors I believe are a good fit - all sponsors are **manually reviewed and approved**. I reserve the right to revoke sponsorship at any time. - All sponsor links will include `?ref=securinglaravel.com` to facilitate referrer tracking in your [analytics platform](https://usefathom.com/ref/VKMRVM?ref=securinglaravel.com). - Security Tips are posted on a rolling schedule (8 days + 1 hour after the previous post). - Sponsors do not pick or influence the Security Tip content. - **In Depth articles are never sponsored.** - I aim to promote up to 5 older Security Tips (from 6+ months ago) each week. Sponsors will be added to these before promotion on a best effort situation. ### 4,000 Subscribers Celebration! 🎉 URL: https://securinglaravel.com/4000/ Last updated: 2025-05-13T01:07:33.000Z **Nice work, you found it! 😁** Since *Securing Laravel* has just hit **4,000** subscribers, it feels right to give a matching discount on premium subscriptions! I considered 40% off, but percentages are boring and overused. So instead, how about **$40 off yearly**, or **$4 off monthly**? Now that you're here, it's time to get your discount: [Get $4 off month! (only $11/month)](https://securinglaravel.com/4000-subscribers-celebration-monthly) [Get $40 off yearly! (only $110/year)](https://securinglaravel.com/4000-subscribers-celebration-yearly) *Since you put in the effort to find this, I figure you’re already motivated to sign up. But just in case you need a little nudge…* When you sign up for premium, you’ll get a brand-new **In Depth** article every month covering a security topic Laravel developers need to understand. You’ll also get access to the [full archive of past articles](https://securinglaravel.com/tag/in-depth/). Some are focused on specific vulnerabilities (like [XSS](https://securinglaravel.com/tag/xss/), [SQLi](https://securinglaravel.com/tag/sqli/), etc.), others dig into concepts (like [magic emails](https://securinglaravel.com/tag/magic-links/)), and I often do deep-dive series like [Pentesting Laravel](https://securinglaravel.com/tag/pentesting-laravel/), [Authentication](https://securinglaravel.com/tag/authentication/), and [What’s New in Laravel 12](https://securinglaravel.com/tag/laravel-12/). Your support also helps me dedicate time to improving Laravel and the PHP ecosystem - contributing to the framework, reviewing PRs, helping maintainers patch vulnerabilities, speaking at conferences, and more. **But don’t just take my word for it…** > "Stephen is one of the preeminent voices in the Laravel security community." \~ Ian Landsman > "Security is an important topic and after reading a couple of your tips, I believe it is worth the money. Thanks from Switzerland !" \~ Roger > "I like taking time and reading your articles a lot. I normally cannot read newsletter types of articles. I really like them." \~ > "Your newsletter is one of few I actually read 🎉" \~ Andreas Herss > "Thanks, Stephen. This is really lovely reading the level of detail, the amount of outside references, and the good natured-ness of your writing. Really, really helpful in making it clear and getting it across both how simple it is to exploit and how simple it is to fix." \~ Jason Stewart > "You are doing some of the most important work out there for Laravel. Thank you!🙏🏼" \~ Jim Hull ### Stephen Is Uncontactable Sale?! 😱 URL: https://securinglaravel.com/stephen-is-uncontactable-sale/ Last updated: 2025-08-10T01:48:23.000Z I will be **completely disconnected** from the internet and uncontactable until Saturday this week, and since we haven't had a sale at **Securing Laravel** in a while, I feel like this is the perfect time to do something totally weird and run a sale! What could possibly go wrong?? 🤣 *(But please don't crash my website! 🙏)* For the next 5 days, only while I'm uncontactable, you can sign up for a new **premium subscription** and get **25% off**. [25% off Monthly! ($11.25/month)](https://securinglaravel.com/stephen-is-uncontactable-monthly) [25% off Yearly! ($112.50/year)](https://securinglaravel.com/stephen-is-uncontactable-yearly) ### **That's Great, But What Do I Get When I Sign Up?** Premium subscribers receive my weekly [**In Depth**](https://securinglaravel.com/tag/in-depth/) articles, which dive deep into the security topics you need to know as a Laravel developer. There are currently 37 In Depth articles to explore - were currently in the middle of a series on [Authentication](https://securinglaravel.com/tag/authentication/), and have just covered [Setting up Two-Factor Authentication](https://securinglaravel.com/in-depth-setting-up-two-factor-authentication/) in your login flow. Before that, I took subscribers through my list of [Top 10 Security Issues in Laravel apps](https://securinglaravel.com/in-depth-laravel-security-audits-top-10-2024/), and a series on my [Penetration Testing process](https://securinglaravel.com/tag/pentesting-laravel/). We've also covered all the major vulnerabilities, like [SQLi](https://securinglaravel.com/in-depth-sql-injection/), [XSS](https://securinglaravel.com/in-depth-escaping-output-safely/), [Mass-Assignment](https://securinglaravel.com/in-depth-mass-assignment-vulnerabilities/), [Clickjacking](https://securinglaravel.com/in-depth-css-clickjacking/), [Enumeration](https://securinglaravel.com/in-depth-registration-without-enumeration/), [IDORs](https://securinglaravel.com/in-depth-insecure-direct-object-references/), and a lot more. Premium subscribers can also suggest topics to cover, which will bump them up to the top of the list, and most importantly Premium subscribers directly fund my work on Securing Laravel and in the Laravel community. I would not be able to spend the time that I do writing In Depth articles, free Security Tips, reviewing packages, making PRs to the framework, and more, if it wasn't for your financial support. ### But Don't Just Take My Word For It... Here's what other folks have said about Securing Laravel, and my work in the community... [!["If you touch Laravel code in any way, you should read Stephen‘s newsletter and follow his every word."](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2025/08/image-3.png)](https://x.com/arvidkahl/status/1950031995133636931?ref=securinglaravel.com) "If you touch Laravel code in any way, you should read Stephen‘s newsletter and follow his every word." \~Arvid Kahl [![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2025/08/image-4.png)](https://bsky.app/profile/coolaphp.bsky.social/post/3lp2hf7mfjs2u?ref=securinglaravel.com) "It's the one newsletter I genuinely try to read, and provides so much value!" \~Dave Coolidge [![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2025/08/image-5.png)](https://x.com/AshAllenDesign/status/1915780592307761413?ref=securinglaravel.com) "His newsletter is a fantastic resource and has helped me improve the security of my Laravel apps. So I'd definitely recommend signing up 🔥" \~Ash Allen [![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2025/08/image-6.png)](https://phpc.social/@outofcontrol/114396109896287127?ref=securinglaravel.com) "If you’ve been sitting on the fence about signing up. I can vouch for the high quality content Stephen delivers. Go signup today, it will be worth your time." \~Out of Control [![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2025/08/image-7.png)](https://www.linkedin.com/feed/update/urn:li:activity:7271334384295636993/?ref=securinglaravel.com) "To anyone interested in improving their general security knowledge, I STRONGLY recommend following [Stephen Rees-Carter](https://www.linkedin.com/in/stephen-rees-carter/?ref=securinglaravel.com), for a small time investment he drops gems that are informative and incredibly useful. Also strongly suggest watching his Laracon talks for an eye opening and entertaining experience." \~Shaun Simpson > "Stephen is one of the preeminent voices in the Laravel security community." \~ Ian Landsman ## Posts ### Security Tip: The Trap That Caught Me! URL: https://securinglaravel.com/security-tip-the-trap-that-caught-me/ Last updated: 2026-09-05T06:00:38.000Z One of the cool parts of doing [Laravel Security Audits and Penetration Tests](https://valorinsecurity.com/?ref=securinglaravel.com) is seeing how my clients go about solving different problems in their code, and some of their creative security implementations. One such example is a class called `LivewireBotBanner`, which I came across during a recent audit. I discovered it by accident when I was attempting to override some Livewire properties that were incorrectly configured. I made a request to a locked property and suddenly hit `403` errors on all requests. I assumed it was a WAF being overly twitchy, rotated my IP address, and got back in. But it didn't feel right, my request shouldn't have looked suspicious - especially not compared to some of the other requests I'd made. So I went digging... I quickly discovered a global middleware called `EnforceBotProtection` which looks for suspicious User-Agents (i.e. `curl`, `wget`, `python`, etc), and that made me smile, but clearly wasn't the cause of my `403` \- Burp's browser (which I use during pentests) isn't that lazy. I kept digging and saw this line in the middleware: ```php if (LivewireBotBanner::isBanned($ip)) { abort(403, 'Forbidden'); } ``` After digging into `LivewireBotBanner`, I realised it was a class dedicated to identifying and blocking anyone making suspicious requests to Livewire. The global exception handler (`\App\Exceptions\Handler`) was configured to catch two notable Livewire exceptions, and call `LivewireBotBanner::ban()` to do the actual banning: ```php if ( $e instanceof ComponentNotFoundException || $e instanceof CannotUpdateLockedPropertyException ) { LivewireBotBanner::ban($request, $e); } ``` Such a simple and elegant trick: `ComponentNotFoundException` is fired when a component that doesn't exist is requested, and `CannotUpdateLockedPropertyException` is fired when the user attempts to change a locked property. Neither of these should happen during normal operation, so they provide strong indications that the user is doing something suspicious. The automatic response to these exceptions was to block the user by IP address, returning a `403 Forbidden`, which feels like a suitable response when you're confident it will only be hit by someone doing something intentionally suspicious. That said, there are a couple of caveats I should mention: 1. **This system blocks by IP address**, and it's trivial to rotate your IP if you know what you're doing. In other words, it won't stop a determined attacker. However, you could enhance this protection by introducing a temporary ban on the user account itself - lock them out for 15 minutes, or send an email with an unlock link. The latter serves a dual purpose of notifying the user of suspicious activity, in case of account takeover. (*In defence of my client, they have public Livewire forms where IP is the only unique identifier available.)* 2. **False positives are possible.** A buggy deploy or misconfigured properties could trigger these exceptions, locking out the user. So ensure you've got suitable tests and QA, and an unlock method. 3. **Couple this with logging and alerting**, so your team are notified if users start getting blocked. This serves as both a warning system for an active attack, and an alert if something breaks and legitimate requests are blocked. **My recommendation** is to explore something like this in your code. When you've got exceptions and errors that should only occur during suspicious activity, having some alerting or active defences in place surrounding these won't cost you much, but may slow down or prevent an attacker from poking at your app further. 🤓 **Full credit for this idea goes to* [***Janos Burkhalter**](https://valuequest.ch/en/about-valuequest/janos-burkhalter/?ref=securinglaravel.com) *at* [**ValueQuest*](https://valuequest.ch/?ref=securinglaravel.com)**, with permission given for me to write about it.* --- ***Found this security tip useful?* 👍** [*Subscribe now*](#/portal/signup) *to get weekly* [*Security Tips*](https://securinglaravel.com/tag/tips/) *straight to your inbox - practical, actionable advice to help you build safer apps.* ***Want to go deeper?* 🤓** *Upgrade to a* [*Premium Subscription*](#/portal/signup) *for exclusive monthly* [*In Depth articles*](https://securinglaravel.com/tag/in-depth/)*. Your support directly funds my security work in the Laravel community.* 🥰 **Need a second set of eyes on your code?** *Book a* [*Laravel Security Audit and Penetration Test*](https://valorinsecurity.com/?ref=securinglaravel.com) *\- or, for something more focused, a lighter-weight* [*Security Review*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com)*.* *Finally, connect with me on* [*Twitter*](http://twitter.com/valorin?ref=securinglaravel.com)*,* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*,* [*phpc.social*](https://phpc.social/@valorin?ref=securinglaravel.com)*, and* [*LinkedIn*](https://www.linkedin.com/in/stephen-rees-carter/?ref=securinglaravel.com)*.* ### 5 years of Securing Laravel! URL: https://securinglaravel.com/5-years-of-securing-laravel/ Last updated: 2026-08-31T02:00:31.000Z Greetings, my friends! Somehow August is almost over (*no idea how that happened, didn't we just have May???*), which means **I've been writing Securing Laravel for 5 years!** **31st August 2026** marks 5 years since my first post, and a lot has changed since then - both in the industry, and for me personally. So, [as is my tradition](https://securinglaravel.com/tag/birthday-retrospective/), I want to take some time to reflect on the past 12 months of Securing Laravel. I first launched **Laravel Security in Depth** (as it was known back then) on 31st August 2021, at the time I was taking a break from working as a developer due to burnout, speaking at *Laracon Online* during COVID, and wanted a new outlet for sharing my security experience with the Laravel community. The name changed to **Securing Laravel**, the site moved from Substack to Ghost, but I kept posting monthly [In Depth](https://securinglaravel.com/tag/in-depth/) articles and weekly [Security Tips](https://securinglaravel.com/tag/tips/)... well, I did until October last year - but we'll get to that. Before we dive into the details, looking at both what was published and what was going on behind the scenes, **I want to thank each and every one of you**. Security isn't a shiny trendy topic that gathers huge momentum off flashy announcements, it's a slow burn, something you need to care about and take time away from other priorities to learn about and explore. So I appreciate everyone who takes the time to read my articles, who cares about writing secure code, and learning more about security. I've received some really encouraging and heartfelt messages of support from many of you over the years, and I thank you all for that. I could not do this without your support. Also, I need to give a very special thanks to my premium subscribers who've stuck with me during the last 12 months, despite pauses and delays with the In Depth articles they pay for! Your trust and support means the world, and I'm lucky to have you. Let's look at the past year and what was published... ## Published Articles For these numbers to make sense, we need to take into account that I took all of October through to January off, restarting articles on the 4th February 2026\. That's four months without any articles. *(I issued credit to everyone's accounts during this time to make up for the missed articles.)* After that, I got back into the schedule until the middle of March, when it all fell apart again. I've been posting infrequently since then, but getting more frequent each month. With that in mind, here are the raw numbers: - [13 Security Tips](https://securinglaravel.com/tag/tips/) - [5 In Depth articles](https://securinglaravel.com/tag/in-depth/) The In Depth articles covered [Email Verification](https://securinglaravel.com/in-depth-email-verification-isnt-as-simple-as-you-think/) (it's not as simple as you think), [Public Livewire Properties](https://securinglaravel.com/in-depth-dont-trust-public-livewire-properties/) (soo much fun during security audits!), [Version Number hijacking](https://securinglaravel.com/in-depth-version-numbers-are-vanity-labels/), a fun vulnerability with an [API, SameSite=None, and CSRF](https://securinglaravel.com/in-depth-three-reasonable-decisions-one-critical-vulnerability/), and a [deep dive into unserialise()](https://securinglaravel.com/in-depth-from-serialised-string-to-rce/) and how to abuse it. While the Security Tips looked at [XSS through Alpine directives](https://securinglaravel.com/security-tip-when-is-xss-not-strictly-xss-but-still-bad/), [bypassing CSPs](https://securinglaravel.com/security-tip-bypassing-content-security-policy-with-base/), [broadcast channels](https://securinglaravel.com/security-tip-consider-all-routes-not-just-web/), [JWTs](https://securinglaravel.com/security-tip-your-jwt-might-be-a-forever-key/), [GET request abuse](https://securinglaravel.com/security-tip-stop-putting-actions-on-get-requests/), a [Signed URL trap](https://securinglaravel.com/security-tip-the-signed-url-trap/), [safely](https://securinglaravel.com/security-tip-safely-updating-dependencies/) [updating](https://securinglaravel.com/security-tip-update-your-packages-yes-this-again/) [packages](https://securinglaravel.com/security-tip-secure-your-repositories-with-laravel-moat/), [Slopsquatting](https://securinglaravel.com/security-tip-have-you-heard-of-slopsquatting/), and [SameSite cookies](https://securinglaravel.com/security-tip-do-you-know-your-samesite-cookies/). Definitely not as many as previous years, but I'm proud of each of these articles and have had some great feedback. It was nice to get back into technical dives with the latest In Depth on `unserialize()`, and Slopsquatting definitely made an impact talking about a new vector many folks hadn't even considered. ### What about AI? Given how much is happening in the world right now with AI, so much of our industry is changing, and the question is: why haven't I written about AI? The answer is simple: I haven't felt confident doing so. I write all of my articles by hand, none of it is AI generated (not-so-humble-brag?), and I need to be confident with what I'm writing and researching before I'll write the article. This slows me down and can limit what I cover, but **I would rather limit my scope than put out badly researched articles and give you the wrong information.** It would be easy to add AI into my workflow to help me write articles faster, to keep up with my schedule, however that's not something I feel comfortable doing. These articles are my voice, thoughts, experience, recommendations, and bad jokes. AI cannot replicate that, and I would rather miss deadlines and send out articles late than compromise their quality. That said, I need to spend some time learning AI Security properly, trying different models and methods, and then write articles about it. To be fair, I do use AI as part of my [security audit work](https://valorinsecurity.com/?ref=securinglaravel.com), however I'm using it from a pentesting/code auditing approach, and have a number of internal tools and scripts that enhance the results. All of this is valuable, but not easily sharable or relatable for developers, so I need to reconcile that information with something suitable for a wider audience. ## Subscribers Last year I reported 4,017 subscribers (both free and paid), and this year that's only slightly higher at **4,070 subscribers**. ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/image.png) Ghost Analytics That's a discouraging number. I expected the growth to be lower, given my activity, however that's not much. That said, the growth for last year (2025) was only 159 - compared with 1,337 for 2024\. So this is probably an indicator of a bigger change. Paid subscribers went from 183 to **142**, which I did expect, given my inactivity and delays with posting. However, it does hurt financially, and means Securing Laravel is no longer paying for the time I spend on it, like it once was. I expected to lose paid subscribers due to my inactivity, however this trend of lower subscribers (both free and paid) has been going on for longer than this year. The industry is changing so much at the moment, AI has replaced so much of our learning, and I believe text-based articles and paid articles are a much harder sell. A question I often ask myself is: when you can ask your AI to teach you something - or just do it for you - why would you read my articles and sign up for my emails? ## Analytics Next up, Analytics from [Fathom](https://usefathom.com/ref/VKMRVM?ref=securinglaravel.com): ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/image-1.png) Analytics for the last 12 months. The previous period comparison paints a pretty clear picture. There were multiple factors working against me here, but not all of them within my control. All I can do is keep persisting and see where we end up. In terms of top pages, the [Livewire RCE](https://securinglaravel.com/security-notice-livewire-v3-rce/) is still top of the list, followed by the popular [Pentesting part 1](https://securinglaravel.com/in-depth-pentesting-laravel-part-1-passive-scans/) article, and then the [APIs responding to HTTP](https://securinglaravel.com/security-tip-how-should-apis-respond-to-http/). ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/image-3.png) Top 10 countries in last 12 months The Top 10 countries is missing Australia 😮, Germany has overtaken the Netherlands, and China has appeared from nowhere! ## The Past Year... ⚠️ ****Warning:** I'm going to be talking honestly about mental and physical health and family issues - a lot more than I normally would. Feel free to skip this section if you need to. I wrote last year about having a "couple of brutal years personally", and this year was no different - in fact, it was worse. I alluded to a bunch of stuff that was going on, and so many of them hit hard in the last 12 months. I was diagnosed with Rheumatoid Arthritis (RA) many years ago and had been successfully managing it via a vegan diet for years, but a couple of years ago it started to get really bad, so bad that I was unable to use my right wrist and knee without immense pain. I went to a new rheumatologist and she diagnosed me with Psoriatic Arthritis (PsA) (the original RA was wrong), and started me on the medication for that. That medication stopped working this year, bringing on even more chronic pain, so now I'm in the "fun" process of testing different drugs to find one that allows me to use my wrist without too much pain. This makes working (especially typing!) difficult some days, and despite trialling multiple fancy keyboards, I still haven't found a good solution for when it gets bad. Alongside this, my ex-wife and I separated near the end of last year, after 16 years of marriage with 2 kids. So when I took time off from October, I was rebuilding my life, figuring out what I was going to do, and finding a new place to live. There is still so much to do, and is by far the hardest thing I've ever been through. The separation combined with chronic pain destroyed my mental health, and my physical health went downhill too. I am incredibly grateful that I was already seeing a psychologist before all of this happened, and am still able to see him every fortnight to work through what's going on in my life. I don't think I would be doing anywhere near as well if I didn't have his support. I also have a special group chat with close friends who have done more for me than they realise. Life is a massive juggling act right now, and I am so incredibly grateful for my clients who are understanding when I need to shift dates to accommodate my health. Securing Laravel unfortunately comes in second to my client work, and most of my delays are due to me simply not having enough usable work time in the day to write an article. One thing that has really helped me this year has been getting into Pottery - every Sunday night for 3 hours. It's the highlight of my week, when I get to ignore technology and throw all of my concentration into working with clay on the wheel, using my hands to feel connected and present. You can find my work over at: [https://pottery.valorin.net/](https://pottery.valorin.net/?ref=securinglaravel.com). ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/PXL_20260824_000113634.jpg) ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/PXL_20260824_000955224.jpg) ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/PXL_20260824_001325602.jpg) ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/PXL_20260801_001121602.jpg) ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/PXL_20260731_235045035.jpg) ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/PXL_20260801_001128691.jpg) Some of my recent pieces ## Looking Ahead For now, I am just working on improving my posting schedule. I am tracking the In Depth articles that are overdue and will keep publishing them until I've caught up - there are currently 2 overdue. I don't have the headspace for anything bigger until I catch up and get back to my schedule, although I do need to figure out where this is all going and if I need to pivot in a different direction. AI is definitely an area I need to start writing, it's too big and too well used to ignore. **A quick question on value:** Paid subscriptions have dropped this year, and I believe it's not just because of my inactivity. It might just be the cost, but rather than asking "*Is Securing Laravel too expensive?*", maybe a better question is: **What would make a premium subscription genuinely worth it to you?** More frequent In Depth articles? Different topics (i.e. AI security, etc)? Something I'm not offering yet? If price genuinely is the blocker for you, tell me that too! ### Thank you. Once again, thank you for being here. If you've made it this far, you obviously care about what I have to say, and that means so much to me. Thank you. 🥰 As I've done in previous years, can I please ask you to do two things: 1. **Leave a comment or send me an email, answering:** 1. What you **love** about *Securing Laravel*. 2. What you think can be improved about *Securing Laravel*. 2. **Please share a recent article with at least one person.** *Maybe forward an email to a colleague with a useful security tip that might be relevant to them, or post it up on your social media of choice?* Thank you, Stephen ### Security Tip: Help Password Managers Get It Right! URL: https://securinglaravel.com/security-tip-help-password-managers-get-it-right/ Last updated: 2026-08-29T05:00:51.000Z /Back in May, Taylor Otwell announced a cool little feature that has been hiding on my list to cover: [![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/image-5-1.png)](https://x.com/taylorotwell/status/2054587937258443170?ref=securinglaravel.com) Taylor's HTML passwordrules generator tweet. > Laravel’s password validation rule can now generate an HTML \`passwordrules\` attribute string. > > Great for helping password managers like 1Password suggest valid passwords. ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/08/image-6.png) HTML passwordrules generator in action The feature [was PRed](https://github.com/laravel/framework/pull/60070?ref=securinglaravel.com) by [Liam Hammett](https://github.com/imliam?ref=securinglaravel.com) and bridges a gap between password rules on the server-side and what is happening in the browser. This can be very useful if your organisation requires specific rules, and you want to make it easy for folks who use password managers to generate passwords that follow the rules. 🤓 **Let's just ignore the 'password rules are bad' argument for now. Organisations have immovable illogical requirements that we, as developers, just need to accept and work within.* As per the PR, this is what it looks like: ```php > use Illuminate\Validation\Rules\Password; > Password::min(12)->max(64)->mixedCase()->numbers()->symbols()->toPasswordRulesString(); = "minlength: 12; maxlength: 64; required: lower; required: upper; required: digit; required: special;" ``` Generating the passwordrules string. To avoid password rules getting out of sync between different code locations, you should define [Default Password Rules](https://securinglaravel.com/security-tip-default-password-rules/) for your app, so you can just call `Password::defaults()->toPasswordRulesString()` inside Blade. ```HTML ``` Rendering the string inside a password field. There is not really much else to say here except that this is a simple improvement that will make life a bit easier for some of your users. It won't take long to implement, but it'll make UX a bit nicer. It also has the happy side effect of making password managers easier to use - a user will be less likely to generate a rubbish password if their password manager can generate a legitimate password. ⚠️ **It's worth pointing out that the* *`passwordrules`* *attribute isn't a standard HTML feature, but rather something* [**Apple added to Safari*](https://developer.apple.com/password-rules/?ref=securinglaravel.com)**. It is also supported by some Password Managers, such as* [**1Password*](https://1password.com/blog/a-smarter-password-generator?ref=securinglaravel.com)**. However, there is no impact if users aren't using a supported browser/password manager, so it's safe to use.* --- ***Found this security tip useful?* 👍** [*Subscribe now*](#/portal/signup) *to get weekly* [*Security Tips*](https://securinglaravel.com/tag/tips/) *straight to your inbox - practical, actionable advice to help you build safer apps.* ***Want to go deeper?* 🤓** *Upgrade to a* [*Premium Subscription*](#/portal/signup) *for exclusive monthly* [*In Depth articles*](https://securinglaravel.com/tag/in-depth/)*. Your support directly funds my security work in the Laravel community.* 🥰 **Need a second set of eyes on your code?** *Book a* [*Laravel Security Audit and Penetration Test*](https://valorinsecurity.com/?ref=securinglaravel.com) *\- or, for something more focused, a lighter-weight* [*Security Review*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com)*.* *Finally, connect with me on* [*Twitter*](http://twitter.com/valorin?ref=securinglaravel.com)*,* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*,* [*phpc.social*](https://phpc.social/@valorin?ref=securinglaravel.com)*, and* [*LinkedIn*](https://www.linkedin.com/in/stephen-rees-carter/?ref=securinglaravel.com)*.* ### In Depth: From Serialised String to RCE! URL: https://securinglaravel.com/in-depth-from-serialised-string-to-rce/ Last updated: 2026-08-26T01:05:48.000Z In my last In Depth article, [we looked at a vulnerability](https://securinglaravel.com/in-depth-three-reasonable-decisions-one-critical-vulnerability/) that involved abusing missing CSRF protection within an unprotected session context to gain access to an API, and I finished by saying "*issues like this pop up in our code all the time*" with a reference to PHP Deserialisation attacks. Which felt like the perfect introduction to focusing on them today. I've [covered them before](https://securinglaravel.com/tag/serialisation/), the first as a generic warning "*this is bad*": [Security Tip: Encoding/Serialising Data\[Tip#36\] Encoding/serialising data can be risky if you’re not using the correct functions.![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/icon/Securing-Laravel-Round-Logo-1-d283119d-4617-4fc3-a17e-15d5376d1d89.png)Securing LaravelStephen Rees-Carter![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/thumbnail/https-3a-2f-2fsubstack-post-media.s3.amazonaws.com-2fpublic-2fimages-2f986442af-cfc4-46df-bb16-1d6681eeba64_1600x900-b9565ffa-cf3d-48d8-88d7-054c04a8c6ab.jpg)](https://securinglaravel.com/security-tip-encodingserialising/) And followed up with a "*here's how you do it safely*": [Security Tip: Can You Safely Unserialise Classes?\[Tip #95\] While you really shouldn’t unserialise anything you get from a user, occasionally you have no choice... so how do you do it safely?![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/icon/Securing-Laravel-Round-Logo-1-75d15920-e941-4d9c-9e9d-082b4a650596.png)Securing LaravelStephen Rees-Carter![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/thumbnail/Securing-Laravel---Mountains--3--a6f2d026-9766-4f51-8296-9a5d0414f420.png)](https://securinglaravel.com/security-tip-can-you-safely-unserialise-classes/) But now I think it's time to explain exactly how they work and why you need to be so careful, plus we'll look at some real examples within the Laravel ecosystem (including my favourite demo attack). ## How Does `serialize()`/`unserialize()` Work? ### `serialize()` The PHP docs describe `serialize()` as: > `function serialize(mixed $value): string` > Generates a storable representation of a value. > This is useful for storing or passing PHP values around without losing their type and structure. > To make the serialized string into a PHP value again, use `unserialize()`. Let's take a look at some basic examples: ``` > serialize(null); = N; > serialize(true); = b:1; > serialize(123); = i:123; > serialize("hello world"); = s:11:"hello world"; > serialize([1, 2, 3]); = a:3:{i:0;i:1;i:1;i:2;i:2;i:3;} > serialize(["one", "two", "three"]); = a:3:{i:0;s:3:"one";i:1;s:3:"two";i:2;s:5:"three";} > serialize(["key" => "one", "foo" => "two", "bar" => "three"]); = a:3:{s:3:"key";s:3:"one";s:3:"foo";s:3:"two";s:3:"bar";s:5:"three";} ``` The serialised string starts with a character that indicates the type, such as `N`, `b`, `i`, `s`, and `a`, and a colon (`:`) to separate the type prefix from *value*, with a semi-colon (`;`) after the value. In the case of `null`, there is no value and the entire thing is `N;`. **Booleans** and **Integers** contain a single value after the colon, either `1` or `0` for boolean `true` and `false`, and the integer value for the integer. **Strings** are a bit more complicated and follow the format: `s::"";`, which first defines the string length (in bytes), before presenting the value - which is wrapped in quotes. This provides a rather elegant way to prevent injection attacks by avoiding the need to escape the value. For example, `hello"world` simply becomes `s:11:"hello"world";`, without the need to escape or encode the `"` in the middle. Compare this to JSON or XML, where you need to escape or encode quotes and/or brackets to avoid breaking the structure. **Arrays** add some more complexity, and follow the format: `a::{;;;;...}` \- with the semi-colon (`;`) used after each key and value . Since PHP's arrays can support anything placed inside of them, this format is recursive, and the array length at the start prevents injection or corruption. It's verbose but solid. **Now, if that was all `serialize()` did, it'd be a brilliant format to use. However, it also supports objects, and this is where things get dangerous...** Let's take a look at a very simple example: ``` > serialize((object) ["key" => "value"]); = O:8:"stdClass":1:{s:3:"key";s:5:"value";} ``` **Objects** start out with the `O` prefix, followed by the length of the class name and the class name itself: `O:8:"stdClass":` \- which identifies which class has been serialised. Following that is the same format as arrays, with the property count and key=value pairs inside the brackets: `1:{s:3:"key";s:5:"value";}`. Objects can also be nested: ``` > serialize((object) [(object) ["key" => "value"]]); = O:8:"stdClass":1:{s:1:"0";O:8:"stdClass":1:{s:3:"key";s:5:"value";}} ``` As a more realistic example, here's what I get when I serialise a User in an app I'm working on: ``` > serialize(User::find(1)); = O:15:"App\Models\User":35:{ s:13:"\0*\0connection";s:6:"sqlite"; s:8:"\0*\0table";s:5:"users"; s:13:"\0*\0primaryKey";s:2:"id"; s:10:"\0*\0keyType";s:3:"int"; s:12:"incrementing";b:1; s:7:"\0*\0with";a:0:{} s:12:"\0*\0withCount";a:0:{} s:19:"preventsLazyLoading";b:0; s:10:"\0*\0perPage";i:15; s:6:"exists";b:1; s:18:"wasRecentlyCreated";b:0; s:28:"\0*\0escapeWhenCastingToString";b:0; s:13:"\0*\0attributes";a:14:{ s:2:"id";i:1; s:4:"name";s:19:"Stephen Rees-Carter"; s:5:"email";s:27:"stephen@valorinsecurity.com"; s:17:"email_verified_at";s:19:"2026-03-30 02:40:19"; s:8:"password";N; s:5:"admin";i:1; s:14:"remember_token";s:60:"..."; s:10:"created_at";s:19:"2026-03-30 02:39:02"; s:10:"updated_at";s:19:"2026-08-03 02:18:15"; ... } s:11:"\0*\0original";a:14:{...} s:10:"\0*\0changes";a:0:{} s:11:"\0*\0previous";a:0:{} s:8:"\0*\0casts";a:7:{ s:5:"admin";s:7:"boolean"; s:6:"system";s:7:"boolean"; s:17:"email_verified_at";s:8:"datetime"; s:22:"scan_notification_mode";s:30:"App\Enums\ScanNotificationMode"; s:19:"last_digest_sent_at";s:8:"datetime"; s:14:"personal_notes";s:9:"encrypted"; s:22:"personal_notes_enabled";s:7:"boolean"; } s:17:"\0*\0classCastCache";a:0:{} s:21:"\0*\0attributeCastCache";a:0:{} s:13:"\0*\0dateFormat";N; s:10:"\0*\0appends";a:0:{} s:19:"\0*\0dispatchesEvents";a:0:{} s:14:"\0*\0observables";a:0:{} s:12:"\0*\0relations";a:0:{} s:10:"\0*\0touches";a:0:{} s:27:"\0*\0relationAutoloadCallback";N; s:26:"\0*\0relationAutoloadContext";N; s:10:"timestamps";b:1; s:13:"usesUniqueIds";b:0; s:9:"\0*\0hidden";a:3:{ i:0;s:8:"password";i:1; s:14:"remember_token";i:2; s:14:"personal_notes"; } s:10:"\0*\0visible";a:0:{} s:11:"\0*\0fillable";a:3:{ i:0;s:4:"name";i:1; s:5:"email";i:2; s:22:"scan_notification_mode"; } s:10:"\0*\0guarded";a:1:{i:0;s:1:"*";} s:19:"\0*\0authPasswordName";s:8:"password"; s:20:"\0*\0rememberTokenName";s:14:"remember_token"; } ``` I've removed the sensitive and repeated data to make it smaller, but I want you to note just how much information is in there. It's not just the **public** properties, but also **protected** (the keys prefixed with `\0*\0`), and **private** (none in this example, but they have a `\0ClassName\0` prefix). Also consider, this is now a string - so any part of it can be modified while it's a string. 💡 Note, protected and private properties are prefixed with `\0*\0` or `\0ClassName\0` in the example, however in the actual serialised string `\0` is a **null-byte -* a special non-typable character that cannot be represented on the page directly. I will leave you with those thoughts as we look into what `unserialize()` does. ### `unserialize()` It should be no surprise what happens with serialized nulls, booleans, strings, integers, and arrays: ``` > unserialize("N;"); = null > unserialize("b:1;"); = true > unserialize("i:123;"); = 123 > unserialize("s:11:\"hello world\";"); = "hello world" > unserialize("a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}"); = [ 1, 2, 3, ] > unserialize("a:3:{i:0;s:3:\"one\";i:1;s:3:\"two\";i:2;s:5:\"three\";}"); = [ "one", "two", "three", ] > unserialize("a:3:{s:3:\"key\";s:3:\"one\";s:3:\"foo\";s:3:\"two\";s:3:\"bar\";s:5:\"three\";}"); = [ "key" => "one", "foo" => "two", "bar" => "three", ] ``` The only interesting thing I think I can add here is that `unserialize()` will throw a warning and return `false` if it is given a string that it cannot unserialise: ``` > unserialize("i:\"abc\";"); WARNING unserialize(): Error at offset 0 of 8 bytes. = false > unserialize("s:11:\"hello world!\";"); WARNING unserialize(): Error at offset 17 of 20 bytes. = false ``` We can follow the logical next step over to our objects, and see them revived: ``` > unserialize("O:8:\"stdClass\":3:{s:3:\"key\";s:3:\"one\";s:3:\"foo\";s:3:\"two\";s:3:\"bar\";s:5:\"three\";}"); = {#9229 +"key": "one", +"foo": "two", +"bar": "three", } > unserialize("O:8:\"stdClass\":1:{s:1:\"0\";O:8:\"stdClass\":1:{s:3:\"key\";s:5:\"value\";}}"); = {#9176 +"0": {#8508 +"key": "value", }, } > $user = serialize(User::find(1)); = "O:15:\"App\\Models\\User\":35:{...}" > unserialize($user); = App\Models\User {#9225 id: 1, name: "Stephen Rees-Carter", email: "stephen@valorinsecurity.com", ... } ``` Again, that's what we'd expect to happen. PHP creates a new instance of the object, and hydrates all of the properties within the serialised string back onto the object. On face value this sounds amazing for storing data in cookies or cache, encrypting values, transferring data between PHP apps, etc. It's clearly far superior to JSON in a lot of ways. **So what's the catch??? 🤨** Remember how I mentioned above that it's trivial to modify the serialised string? Well, `unserialise()` doesn't just hydrate properties, it can also trigger some magic methods... _This post is for paying subscribers only._ ### Security Tip: Do You Know Your SameSite Cookies? URL: https://securinglaravel.com/security-tip-do-you-know-your-samesite-cookies/ Last updated: 2026-08-03T06:07:21.000Z As [promised last time](https://securinglaravel.com/in-depth-three-reasonable-decisions-one-critical-vulnerability/#dangers-of-samesitenone), it's time to look at `SameSite` cookies. Most developers will have heard them mentioned, and maybe even changed the value in Laravel, but how well do you really know about what they do and why? Let's consider this cookie header: ``` laravel-session=eyJpdiI6Im...IjoiIn0%3D; expires=Mon, 03 Aug 2026 06:14:17 GMT; Max-Age=7200; path=/; secure; httponly; samesite=lax ``` Cookie headers follow a simple format, first is the actual cookie value (`eyJpdiI6Im...IjoiIn0%3D`), followed by a series of semi-colon (`;`) delimited keywords and `key=value` pairs. Hiding right at the end here is `samesite=lax` \- which instructs the browser this cookie can only be included when it follows the `lax` rules. 💡 Before I explain `lax` (and `strict` and `none`), it is important to understand that "**same-site*" means anything on the same registrable domain, such as `securinglaravel.com`, `subdomain.securinglaravel.com`, `www.securinglaravel.com`. This means cookies sent to `securinglaravel.com` will also go to `www.securinglaravel.com`, etc. The exception to this are domains on the [Public Suffix List](https://publicsuffix.org/?ref=securinglaravel.com), such as `github.io`, and `laravel.cloud`, which shift the **same-site* boundary onto their subdomains (i.e. `pottery.laravel.cloud` vs `valorin.laravel.cloud`). There are three `SameSite=` options: - **`SameSite=Strict`** - Cookies are only ever sent on *same-site* requests, with no exceptions. - This includes users clicking a link from a *cross-site* origin (i.e. clicking a link on `laravel.com` that leads to `securinglaravel.com`) - the cookies will not be included. - The result is that existing user sessions will **not** be resumed and a new session will be created. - This is incredibly useful for sensitive cookies or high-security contexts, where you **do not** want a user landing on your site within an authenticated session from an external source. - **`SameSite=None; Secure`** - Cookies are **always** sent, no matter where the request comes from or how it is triggered. - It **requires** the [additional Secure keyword](https://securinglaravel.com/security-tip-auto-secure-cookies-ftw/), which tells the browser that cookie requires an `https` connection to be sent. The browser will reject `SameSite=None` cookies without `Secure`. - Commonly used for embedded widgets, forms, frontends, and APIs where requests are being made *cross-site*. - Because the browser sends these cookies automatically, any session they authenticate needs CSRF protection on state-changing requests. (As opposed to token-based API auth, where the credential isn't sent automatically and can't be forged.) - If you need `SameSite=None`, I recommend a separate set of cookies so your primary auth cookies remain `Lax`. (Which leads us to...) - **`SameSite=Lax`** - Cookies are only sent during *top-level navigation via a safe method*, or put simply when clicking a link between *cross-site* domains that triggers a `GET` request. - Cookies are **not** sent during non-`GET` requests (such as form submissions, `fetch()`, etc), or for embedded/loaded content (iframes, images, scripts). - This prevents an attacker from injecting payloads or triggering actions without the user's knowledge or permission, making it the **sane default** suitable for most apps. - Default option in Laravel [config/session.php](https://github.com/laravel/laravel/blob/13.x/config/session.php?ref=securinglaravel.com#L202): `'same_site' => env('SESSION_SAME_SITE', 'lax')` - This is the default option in most browsers, **but not all** *(Safari & Firefox* 😕*)*, so you should always define it to ensure it is correctly set. **Isn't that just 500 words to tell me about something that is already default in Laravel?** ~~Well, yes, but now you know about `None` and `Strict` 😈.~~ Well, yes, but I know from experience auditing many Laravel apps that `SameSite=None` is **incredibly common**, and it opens the door to [CSRF](https://securinglaravel.com/tag/csrf/) attacks ([as we saw here](https://securinglaravel.com/in-depth-three-reasonable-decisions-one-critical-vulnerability/)) in your apps. So my goal is to teach how `SameSite` works so if you need to reach for `SameSite=None`, you'll be warned and know how to do it safely. --- ***Found this security tip useful?* 👍** [*Subscribe now*](#/portal/signup) *to get weekly* [***Security Tips***](https://securinglaravel.com/tag/tips/) *straight to your inbox, filled with practical, actionable advice to help you build safer apps.* ***Want to learn more?* 🤓** *Upgrade to a* [*Premium Subscription*](#/portal/signup) *for exclusive monthly* [**In Depth* articles*](https://securinglaravel.com/tag/in-depth/)*! Your support directly funds my security work in the Laravel community.* 🥰 **Need a second set of eyes on your code?** *Book in a* [*Laravel Security Audit and Penetration Test*](https://stephenreescarter.net/laravel-security-audits-and-pentesting/?utm%5Fsource=securinglaravel.com) *today! I offer budget-friendly* [*Security Reviews*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com) *too.* *Finally, connect with me on* [*Twitter*](http://twitter.com/valorin?ref=securinglaravel.com)*,* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*,* [*phpc.social*](https://phpc.social/@valorin?ref=securinglaravel.com)*, and* [*LinkedIn*](https://www.linkedin.com/in/stephen-rees-carter/?ref=securinglaravel.com)*.* ### In Depth: Three Reasonable Decisions, One Critical Vulnerability URL: https://securinglaravel.com/in-depth-three-reasonable-decisions-one-critical-vulnerability/ Last updated: 2026-07-20T06:41:39.000Z 🕵️ **Quick note: this is exactly the sort of thing I find during a* [**Laravel Security Audit and Penetration Test*](https://valorinsecurity.com/?ref=securinglaravel.com)**, and I have a few openings over the next few months. Reach out and let's talk.* Let's take a break from looking at [Supply Chain](https://securinglaravel.com/tag/supply-chain/) issues to explore a fun vulnerability I found a couple of months ago during a [security audit](https://valorinsecurity.com/?ref=securinglaravel.com), as there are a few lessons to be learned about how minor weaknesses can compound into a bigger vulnerability with significant impacts. 🙊 **Note, I've changed names and details to protect my client's privacy, however the root cause and impacts of the vulnerability remain. (They also fixed this issue really quickly - but more on that later.)* ## Background Let's set the scene - my client's project has a couple of key features we need to be aware of: 1. Livewire app, with some legacy Vue components. 2. Provides an API for their users, using API tokens. 3. The Vue components use the API to communicate with the server. 4. Session cookies authenticate into the API for the legacy Vue components. This application is primarily a Livewire application, but it includes some legacy Vue components. To make things simple, the Vue components interact with the server using the same API that users have access to. However, rather than generating an API token for Vue to use on demand in the browser, they added Middleware before the API layer that translates the cookie-based session into an API key `Authorization: Bearer ` header injected directly into the Request object. When the API initiates, it sees the *Bearer Token* and authenticates the user. On face value, it sounds like a clever solution: The Vue components just work because the session cookie is there, and bearer tokens work for the API for external users. I can see the appeal - the Vue components are old and fragile, and until they complete the migration to Livewire, handling authorization elsewhere feels like a safe move. However, the Session Cookie was also marked as `SameSite=None`... ## Dangers of SameSite=None Surprisingly I haven't really talked much about the `SameSite` cookie attribute on Securing Laravel (I will need to fix that), so let me quickly explain what it does: `SameSite` instructs the browser when cookies are allowed to be sent, both in terms of same-site vs cross-site requests and the type of requests. - **Same-Site Requests** are requests between the same domain (or subdomain). For example, when you have `securinglaravel.com/tip-1` open in the browser and click on a link for `securinglaravel.com/tip-2`. The browser recognises the domain is the same, and applies the security context required. Subdomains are also included, so `alpha.securinglaravel.com` \-> `beta.securinglaravel.com` is also considered the same site. - **Cross-Site Requests** are requests between different domains - such as going from `securinglaravel.com` through to `laravel.com`. There are three values we can set for `SameSite` on our cookies: - **`SameSite=Strict`** blocks all Cross-Site requests. Not only does this include form requests via `POST`, Javascript calls via other methods, and any form of embedding content (such as ``), but it also blocks `GET` link clicks. - **`SameSite=None`** allows all Cross-Site requests. Any request, no matter how it is formed, includes the cookie. *(`SameSite=None` also requires the `Secure` attribute and an `https://` connection.)* - **`SameSite=Lax`** is a combination of the two (and is also the default value applied by most browsers when you don't define it): - *Safe Requests* are basically just top level navigation using `GET`, such as clicking on links between sites. These are the safe scenarios where you expect an existing session on the target site to resume immediately. - *Unsafe Requests* are everything else. This includes non-`GET` requests (forms, Javascript, etc), embedded or loaded content (frames, scripts, images, etc). Cookies are completely blocked within these requests. 💡 **Side note:* *`SameSite=Lax`* *is not the default in Firefox or Safari, so it's important to define* `SameSite=Lax` to gain its full protection. I'll write a dedicated article about SameSite cookies soon - all you need to know for now is that **`SameSite=None` disables security controls and allows session cookies to be included on all requests.** As I said above, the session cookie that allowed API access was set with `SameSite=None`. This is also a concern, however there is one thing we haven't even talked about yet, that turned this from bad to critical... _This post is for paying subscribers only._ ### Security Tip: Have You Heard Of Slopsquatting? URL: https://securinglaravel.com/security-tip-have-you-heard-of-slopsquatting/ Last updated: 2026-07-01T01:51:28.000Z You've probably heard of *Squatting* before (i.e. *"the act of occupying an abandoned or unoccupied area of land or a building without lawful permission"*), especially in the context of: - **Cybersquatting / Namespace squatting / Domain squatting** \- registering unused or expired domains or packages relating to a brand or trademark, to sell back at a profit. - **Typosquatting** \- registering similar domains or packages to trick users into misreading it, such as `go0gle.com` , `paypa1.com`, `laraval/laravel`. - **Combosquatting** \- Combining brand names with a relevant word, such as `apple-support.com`. - **Soundsquatting** \- abusing names that sound the same out loud, such as `four` vs `for`. But did you know we've got a new one? **Slopsquatting!** And yep, you guessed it - this relates to registering package names that coding agents hallucinate! Consider the scenario: Claude or GPT is working hard on your app, realises it needs a dependency, hallucinates the name of the dependency it needs, and confidently runs: ```bash composer require laravel-official/nightwatch ``` A human would look at it and know `laravel-official` isn't the correct namespace, but when your Agent is happily working away in *Auto Mode*, it'll run the command, and since the package actually exists (because the attacker registered it), it'll be installed. The Agent will keep doing their thing, oblivious to what has just happened. **By the time a human checks the prompt, sees what's been installed, it's way too late to do anything. The malware hiding in the package has been executed.** According to the [research into Slopsquatting](https://socket.dev/blog/slopsquatting-how-ai-hallucinations-are-fueling-a-new-class-of-supply-chain-attacks?ref=securinglaravel.com), 19.7% of recommended packages don't exist, with **43% of hallucinated package names repeated every time** (and that number rises to 58% for package names repeated multiple times). This is pretty concerning - if an Agent can repeatedly hallucinate the same package names, then an attacker can reliably identify hallucinations and create packages to match them. **So how do we protect ourselves?** 1. **Don't let your agent install packages.** All of the Agent tools include configuration options to require explicit approval for running `composer` and `npm` commands. Configure these so your Agent's will stop and ask for permission every time. It may slow you down, but it's better than being infected with malware. 2. **Don't blindly approve installs.** Not only do you need to manually approve installs, but actually look into the package the Agent wants to install. Check how popular it is, when the last update was, etc. You want actively maintained and popular packages. 3. **Enable Minimum Release Age.** [We talked about this last time.](https://securinglaravel.com/security-tip-safely-updating-dependencies/) This setting will prevent your Agent from installing packages that were freshly created in response to a new hallucination being identified - which is very common with slopsquatting. 4. **Research and decide on a package before asking the Agent to do the work.** Don't overlook the power of planning out the work your Agent is doing, and making the decision of which package(s) need to be installed before the work is started. This gives you more time to properly research your options. You can definitely use your Agent to help with this research too - just make sure you manually verify the package that gets selected. It may be easy to think about Agentic development as leaving the AI to do all the work, but security is **always** going to be an important part of our jobs. AI is fallible and AI makes mistakes, and to be fair, so do Humans. However when both the Human and the AI are thinking about security, you're more likely to end up with a secure app than when neither of you are. --- ***Found this security tip useful?* 👍** [*Subscribe now*](#/portal/signup) *to get weekly* [***Security Tips***](https://securinglaravel.com/tag/tips/) *straight to your inbox, filled with practical, actionable advice to help you build safer apps.* ***Want to learn more?* 🤓** *Upgrade to a* [*Premium Subscription*](#/portal/signup) *for exclusive monthly* [**In Depth* articles*](https://securinglaravel.com/tag/in-depth/)*! Your support directly funds my security work in the Laravel community.* 🥰 **Need a second set of eyes on your code?** *Book in a* [*Laravel Security Audit and Penetration Test*](https://stephenreescarter.net/laravel-security-audits-and-pentesting/?utm%5Fsource=securinglaravel.com) *today! I offer budget-friendly* [*Security Reviews*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com) *too.* *Finally, connect with me on* [*Twitter*](http://twitter.com/valorin?ref=securinglaravel.com)*,* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*,* [*phpc.social*](https://phpc.social/@valorin?ref=securinglaravel.com)*, and* [*LinkedIn*](https://www.linkedin.com/in/stephen-rees-carter/?ref=securinglaravel.com)*.* ### Security Tip: Safely Updating Dependencies URL: https://securinglaravel.com/security-tip-safely-updating-dependencies/ Last updated: 2026-06-20T04:24:49.000Z I've [written](https://securinglaravel.com/security-tip-update-your-packages-yes-this-again/) [many](https://securinglaravel.com/security-tip-keep-dependencies-updated/) [times](https://securinglaravel.com/security-tip-keep-your-tools-updated/) [about](https://securinglaravel.com/tag/updates/) keeping your dependencies updated, and it used to be an easy process: you'd designate one day a week, fortnight, or month, jump into your command line and run: ```bash composer update && npm update ``` Wait for it to finish, run your tests, and commit. Job done. **But now?** As we [talked about last week](https://securinglaravel.com/in-depth-version-numbers-are-vanity-labels/), dependencies are being targeted with malicious releases, and if one of your dependencies gets compromised, your seemingly innocent `npm update` could pull in a [worm](https://www.akamai.com/blog/security-research/mini-shai-hulud-worm-returns-goes-public?ref=securinglaravel.com) that infects your environment, scrapes your tokens, infects packages you maintain, and more... As a friend said to me recently: > "I've been honestly scared to do those updates." **So what do we do??** The first thing you should do (somewhat ironically) is to update both `composer` and `npm` (or whatever tool you use instead) to the latest versions, **and** keep them updated. The package manager communities are working hard on security updates at the moment, so pulling in the latest versions will get you those updates and default behaviours which are designed to keep your apps safer. [Composer 2.10](https://blog.packagist.com/composer-2-10-release/?ref=securinglaravel.com) has added a Malware Policy that uses a malware threat feed from [Aikido](https://www.aikido.dev/?ref=securinglaravel.com) to identify flagged package versions. With this update, package versions identified as malicious will no longer be available to install or update, and versions with security advisories can be installed but not updated to. Composer will also no longer fallback to downloading the raw source if a distribution artifact fails - which could be abused when dealing with custom composer repositories. **Minimum Release Age** Once you've updated these tools, **Minimum Release Age** is the next thing you want to enable. This defines how old a release needs to be before it is allowed to be used for your app. For example, if you set it to *3 days*, an update will only be applied after it has been available for 3 days. This essentially gives security researchers 3 days to notice and block a malicious version. [npm added it](https://docs.npmjs.com/cli/v11/using-npm/config?ref=securinglaravel.com#min-release-age) in v11.10.0 and you define it using the [min-release-age](https://docs.npmjs.com/cli/v11/using-npm/config?ref=securinglaravel.com#min-release-age) flag in your `.npmrc` file, specifying the number of days: ``` min-release-age=3 ``` `.npmrc` ⚠️ **The* ***Minimum Release Age** *feature is currently only available in NPM, but it is* [**coming soon to Composer*](https://blog.packagist.com/an-update-on-composer-packagist-supply-chain-security/?ref=securinglaravel.com)**.* The downside of *Minimum Release Age* is you lose immediate access to critical security updates - and those 3 days could be critical for an actively exploited vulnerability. However, my recommendation here is to keep it enabled and if a security fix comes along, either lower it or use [min-release-age-exclude](https://docs.npmjs.com/cli/v11/using-npm/config?ref=securinglaravel.com#min-release-age-exclude) to get the update sooner. When Composer rolls this feature out, it'd be a good idea to enable it too. For npm packages when your app is a Laravel app, a couple of days should be safe - there are a limited set of vulnerabilities that can be exploited in your front end (although XSS would still be a critical issue!). When it comes to Composer, I'd be inclined to look at hours instead - security updates are a lot more important when your server is potentially exposed. But you really need to define your own risk levels here. **Disable Scripts** Another simple thing you can do is disable scripts and plugins in Composer when you don't need them to run: ```bash composer install --no-plugins --no-scripts ``` This option is also available for npm: ```bash npm install --ignore-scripts ``` This is incredibly useful when you're downloading third party code for review with no intention of actually running it locally. It should prevent any malicious code included within any of the installed packages from being executed, which is usually how these attacks propagate. **Other Tools** This is getting a bit long for a tip, so let's wrap up with some brief mentions of other tools you can use: - [**Canary Tokens**](https://canarytokens.org/?ref=securinglaravel.com) \- install these in all of your environments and you'll get notified if anyone is sniffing around or harvesting credentials, rather than after a successful compromise when you're cleaning up the mess. - [**Socket Firewall**](https://socket.dev/blog/introducing-socket-firewall?ref=securinglaravel.com) \- a free security tool that prevents supported package managers (`npm`, `yarn`, `pnpm`, `pip`, `uv`, and `cargo`) from installing malicious versions, provided by [Socket](https://socket.dev/?ref=securinglaravel.com), a third-party supply chain security company. *If you have other suggestions or know of other tools that help with this, let me know in the comments so others can see them too.* --- ***Found this security tip useful?* 👍** [*Subscribe now*](#/portal/signup) *to get weekly* [***Security Tips***](https://securinglaravel.com/tag/tips/) *straight to your inbox, filled with practical, actionable advice to help you build safer apps.* ***Want to learn more?* 🤓** *Upgrade to a* [*Premium Subscription*](#/portal/signup) *for exclusive monthly* [**In Depth* articles*](https://securinglaravel.com/tag/in-depth/)*! Your support directly funds my security work in the Laravel community.* 🥰 **Need a second set of eyes on your code?** *Book in a* [*Laravel Security Audit and Penetration Test*](https://stephenreescarter.net/laravel-security-audits-and-pentesting/?utm%5Fsource=securinglaravel.com) *today! I offer budget-friendly* [*Security Reviews*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com) *too.* *Finally, connect with me on* [*Twitter*](http://twitter.com/valorin?ref=securinglaravel.com)*,* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*,* [*phpc.social*](https://phpc.social/@valorin?ref=securinglaravel.com)*, and* [*LinkedIn*](https://www.linkedin.com/in/stephen-rees-carter/?ref=securinglaravel.com)*.* ### In Depth: Version Numbers Are Vanity Labels URL: https://securinglaravel.com/in-depth-version-numbers-are-vanity-labels/ Last updated: 2026-06-08T12:20:52.000Z Following on from [last week's Security Tip](https://securinglaravel.com/security-tip-secure-your-repositories-with-laravel-moat/) about using [Laravel Moat](https://github.com/laravel/moat?ref=securinglaravel.com) to protect our repositories, I want to dive into the mechanics of how *supply chain attacks* work - specifically tag hijacking. There is a surprising amount to cover, and it reveals just how fragile the user-friendly scaffolding we've built really is, and thus why we're seeing so many successful attacks at the moment. Something I say a lot when talking about password security is that **humans are forgetful and lazy**, so we'll reach for simple passwords that are easy to remember. Funnily enough, this logic applies to dependencies too - we use friendly version numbers everywhere (`v1.2.3`), instead of commit SHA hashes (`06e1e8ef749ba9f9daa8cf561c2f94ab696e1b3d`), because version numbers are **so much easier to use**. But here's the catch: **version numbers aren't immutable!** Version numbers are a tag that has been applied to a specific commit, however this tag is simply a vanity label. It can be removed from one commit and assigned to another. Which means an attacker who can manipulate tags on a repository can swap out the legitimate version for a malicious one - and any tool pulling in that tag won't notice the difference *(under the right conditions)*. This loophole makes hijacking packages and injecting malicious versions powerful, although let's not forget that incrementing a version number for malicious versions is still highly effective - all you need is access to the repository. We're not just dealing with local package managers either, but also tools like CI (Continuous Integration) systems, such as GitHub Actions, which also rely on version numbers through tags. Ultimately, any system that relies on `v2` as the indicator for a safe version is at risk if `v2` can be redirected or `v2.1` can be added. Let's dig into it! ## What Happened With `tj-actions/changed-files` and `Laravel-Lang`? Before we look at the mechanics and how all the pieces fit together, let's take a look at two recent examples of this attack in action. ### `tj-actions/changed-files` The [tj-actions/changed-files](https://github.com/tj-actions/changed-files?ref=securinglaravel.com) GitHub Action [was compromised](https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised?ref=securinglaravel.com) through a stolen Personal Access Token (PAT) for a maintenance bot, which had access to manage the repository. Because GitHub forks share the same internal git object storage, a commit pushed to a fork can be loaded by hash from the parent repository, which allowed the attacker to push a malicious commit to a fork and update the tags on the repository to point to this malicious commit. ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/06/image.png) Malicious commit hash set on every tag. With all tags pointing to the malicious commit, every CI workflow that referenced an updated tag loaded the malicious code. This code caused CI secrets/keys to be dumped into the build logs - which are public - allowing the attacker (and anyone else) to harvest these secrets and keys. This was all made possible because CI workflows were referring to the Action based on the version, and the system had no way to verify if it was getting the actual version: ```yaml - name: Get changed files id: changed-files uses: tj-actions/changed-files@v44 ``` The `tj-actions/changed-files` action was used by over 23,000 repositories at the time, and the attack was assigned [CVE-2025-30066](https://nvd.nist.gov/vuln/detail/cve-2025-30066?ref=securinglaravel.com). 💡 **The* [**GitHub issue*](https://github.com/tj-actions/changed-files/issues/2463?ref=securinglaravel.com) *is worth a read if you want a raw chronological narrative of the attack being discovered and investigated by various folks, looking at possible attack vectors, mitigations, etc.* ### `Laravel-Lang` Similar to the previous attack, a Personal Access Token belonging to one of the `Laravel-Lang/*` team members was obtained by the attacker from a [recent GitHub data leak](https://github.blog/security/investigating-unauthorized-access-to-githubs-internal-repositories/?ref=securinglaravel.com), who then used it to update release tags on the targeted packages, pointing them towards malicious commits they had pushed into the repositories. Once the tags were in place, any new `composer install` or `composer update` pulled in the malicious commits, introducing malware into the victim's environment. This malware was hidden inside `src/helpers.php`, and autoloaded through `composer.json:autoload.files` \- which loads the malicious code every time Composer's autoloader is initiated. Scarily, as the package maintainers tried to stop the attack and clean up tags, the attackers continued to recreate them - in the maintainer's own words, *they were being restored "right before our eyes."* See their [full account](https://habr.com/en/articles/1038568/?ref=securinglaravel.com) for the details. It's worth pointing out that this attack would not have worked when an existing `composer.lock` was present alongside `composer install`, as that uses the package commit hash to lock down versions. However, running `composer install` without `composer.lock` or `composer update` would have pulled in the malicious version. As reported by [Snyk](https://snyk.io/blog/laravel-lang-supply-chain-advisory/?ref=securinglaravel.com), 700 versions were compromised across four packages (`laravel-lang/lang`, `laravel-lang/http-statuses`, `laravel-lang/attributes`, and `laravel-lang/actions`), and an earlier report by [Aikido](https://www.aikido.dev/blog/supply-chain-attack-targets-laravel-lang-packages-with-credential-stealer?ref=securinglaravel.com) caught \~233 before the attack continued. ## How Git Tags Actually Work So how does a friendly little `v2.1` end up handing over your AWS keys, and why is it just a vanity label? _This post is for paying subscribers only._ ### Security Tip: Secure Your Repositories with Laravel Moat URL: https://securinglaravel.com/security-tip-secure-your-repositories-with-laravel-moat/ Last updated: 2026-05-26T03:18:26.000Z It's hard to miss all the chaos going on right now in the world of technology and security. Claude Mythos just found [19 previously unknown vulnerabilities in Symfony and Twig](https://symfony.com/blog/claude-mythos-audited-symfony-and-found-19-vulnerabilities?ref=securinglaravel.com) with no false positives, the PHP Foundation [set up a Security Team](https://thephp.foundation/blog/2026/05/18/announcing-ecosystem-security-team/?ref=securinglaravel.com) to help secure the ecosystem, and - most worryingly of all - a number of popular Composer packages [have been hit](https://socket.dev/blog/malicious-postinstall-hook-found-across-700-github-repos?ref=securinglaravel.com) with [supply chain attacks](https://www.aikido.dev/blog/supply-chain-attack-targets-laravel-lang-packages-with-credential-stealer?ref=securinglaravel.com). Unless you have a dedicated security team in your company, it's honestly hard to keep up! Unlike other advancements and changes in our ecosystem, such as new frameworks, coding patterns, and dev tools, security is the one area we cannot ignore. If your application or accounts are vulnerable, then you're at risk, and given how fast everything is moving, it's probably a matter of 'when' rather than 'if'. **So what do we do? How do we protect ourselves?** ~~Give up tech and take up pottery.~~ Start at your public boundaries first and work inwards. Figure out all of the pieces in your ecosystem, so you can start protecting them. 1. Do you maintain or contribute to public repositories? 2. What about private repositories, both personal and as part of your work? 3. What applications do you have 'in the wild'? (Including testing, staging, etc) 4. Who else can access your public repositories, and/or manage your applications? 5. What third party applications have access to your code, your applications, your environments? 6. What package managers do you pull code from? How often do you update? 7. What code is on your development machine? 8. What API keys and credentials are on your development machine within your code? 9. How do you store passwords and sensitive data on your machine? 10. What else is on your machine, and what can it access inside your networks? We've covered some of these before, and we'll tackle the rest in coming weeks, but for now, let's start with #1: protecting our public (and private) repositories. To do this, we can reach to a [newly released tool](https://x.com/enunomaduro/status/2058876431330115996?ref=securinglaravel.com) from [Nuno Maduro](https://x.com/enunomaduro?ref=securinglaravel.com) on the Laravel team: **Laravel Moat**: [https://github.com/laravel/moat](https://github.com/laravel/moat?ref=securinglaravel.com). Here's what the readme says: > **Moat** reviews the security posture of your **GitHub organization** and **repositories**, then surfaces recommendations to consider. It inspects the security controls GitHub already offers — 2FA enforcement, branch protection, signed commits, secret scanning, Dependabot alerts, workflow permissions, pinned actions, repository webhooks, and more — and reports which ones are not enabled or not configured in line with common recommendations. > > ... > > **What Moat is — and what it is not.** Moat is a read-only review tool. It does **not** modify any settings, harden your repositories, prevent intrusions, or remediate compromises. It surfaces **suggestions** based on GitHub's own security settings; it is your responsibility to evaluate each one in the context of your project and decide whether to apply it. A clean Moat report does not certify that an account is secure, nor does a failing report mean it has been compromised. I ran it on [valorin/random](https://github.com/valorin/random?ref=securinglaravel.com), and clearly I've got some work to do... ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/05/image.png) ✓ 11 passed ✕ 7 fails ! 0 warnings — 0 skipped · 18 total This tip is already getting a bit long, so I'll just send you over to the doc to read more, but honestly, just go install and run the tool. It'll spit out a bunch of findings, the reasoning why, and the suggested fix. For example, here is one of mine: ![Currently: 1 repository leave release branches unlocked against force pushes or deletions. Why: Force pushes and branch deletions rewrite history — an attacker (or a tired maintainer) can erase the audit trail of a malicious commit or quietly replace a tagged release with a different tree.](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/05/image-1.png) FAIL: Repositories release branches are locked Take your time and work through each finding - don't rush this! Each finding provides reasoning, the suggested fix, and sometimes a warning you need to consider. It should give you enough information to learn what the controls are, and where to find them within GitHub. From there, you can figure out how best to configure it for your repositories - you may even need to leave it disabled, but that's a decision you need to make, rather than assuming it's currently correct. We'll look into some of Moat's checks, and other related controls in future weeks, but please reach out if there are specific controls you'd like me to cover. Or anything else related to defending your apps and yourself online. **With everything going on in the security world right now, here's your friendly reminder that I offer* ***Security Consulting** *and* ***Penetration Testing** *for Laravel and PHP apps and teams. Tools like Moat are a great first pass, but they can only catch what's machine-checkable. The logic flaws, auth bypasses, and context-specific bugs are the ones that take a human reading your actual code, which is what I do. Better yet, I'll work with you to fix what I find, not just hand you a list and wish you luck.* [Find out more... ](https://valorinsecurity.com/?ref=securinglaravel.com) --- ***Found this security tip useful?* 👍** [*Subscribe now*](#/portal/signup) *to get weekly* [***Security Tips***](https://securinglaravel.com/tag/tips/) *straight to your inbox, filled with practical, actionable advice to help you build safer apps.* ***Want to learn more?* 🤓** *Upgrade to a* [*Premium Subscription*](#/portal/signup) *for exclusive monthly* [**In Depth* articles*](https://securinglaravel.com/tag/in-depth/)*! Your support directly funds my security work in the Laravel community.* 🥰 **Need a second set of eyes on your code?** *Book in a* [*Laravel Security Audit and Penetration Test*](https://stephenreescarter.net/laravel-security-audits-and-pentesting/?utm%5Fsource=securinglaravel.com) *today! I offer budget-friendly* [*Security Reviews*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com) *too.* *Finally, connect with me on* [*Twitter*](http://twitter.com/valorin?ref=securinglaravel.com)*,* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*,* [*phpc.social*](https://phpc.social/@valorin?ref=securinglaravel.com)*, and* [*LinkedIn*](https://www.linkedin.com/in/stephen-rees-carter/?ref=securinglaravel.com)*.* ### Security Tip: The Signed URL Trap URL: https://securinglaravel.com/security-tip-the-signed-url-trap/ Last updated: 2026-04-28T07:49:22.000Z \`If you've been following my work for a while, you'll know that I absolutely love [Signed URLs](https://securinglaravel.com/tag/signed-urls/), but there is one very subtle trap you may accidently fall into when using them. Consider the scenario where you want to provide a [Magic Login Link](https://securinglaravel.com/tag/magic-links/) to the user via email. The workflow may look something like this: 1. User visits `/login` and enters their email address. 2. Application stores `user.email=bilbo@baggins.com` in the session, and generates a magic link using: `URL::temporarySignedRoute('login.magic', now()->addMinutes(5))`. 3. User receives the link via email and clicks link. 4. App validates the Signed URL, and logs user into account matching `user.email`. Sounds fairly straightforward, right? The Signed URL prevents the link from being forged or modified, and using `user.email` from the session ensures the emailed link cannot be hijacked in a different browser or session... **or does it? 🤨** Let's take a closer look at the Signed URL that gets generated: ``` https://example.com/login/magic?expires=1777357944&signature=28770f36a0285739e5efcec7fd4cb68fe3a7733b7c171c0fe1b4f61a31861523 ``` Notice the problem yet? Let's strip off the `expires` and `signature`, as they are added by the Signed URL generator: ``` https://example.com/login/magic ``` What about now? **This URL is not unique!** There is nothing in this URL to tie it to the originating user session, which means the Signed URL will work on **any pending login session,** making it trivial to hijack **any** user account. To exploit the attack, all you need to do is initiate a login for an account with an email address you control, which ensures you receive a valid Signed URL. Once you've done that, simply initiate a new login session for your target account, which puts `user.email=target@example.com` in the session. The existing Signed URL you were sent will still pass validation and the target's email will be inside `user.email` within your session, letting you walk straight in. 💡 **Yes, the target will receive the Magic Link email and may get suspicious, but realistically most folks simply ignore these. Plus, even if they do actually do something, the attacker will already be inside the account and likely have already done what they wanted to do.* **So how do we protect against this?** This attack isn't just about authentication links, it affects **any** Signed URLs where the user context/session matters. This could be file downloads, article previews, invite links, etc. The simplest way to solve it is to inject something within the URL to tie it to the specific user or session. In the case of our magic login link, I would shove the email address in there: ``` > URL::temporarySignedRoute('login.magic', now()->addMinutes(5), ['email' => 'bilbo@baggins.com']) = "https://example.com/login/magic?email=bilbo%40baggins.com&expires=1777359216&signature=2975e64abb35675c54a109cf7e2d769c557d1ff02970363e5ae4576060e0f531" ``` And then after validating the signature in the URL, also validate the email in the URL matches the email in the session `user.email`. This ensures that the magic link can only ever be used for that specific session for that specific user. It may feel like redundant information, but **it's there for uniqueness.** Signed URLs work because the signature verifies that it hasn't been modified, but when the raw URL is `/login/magic` there is nothing to modify - the signature will always be the same, regardless of context or user. Adding the specific email address makes the URL unique, which changes the signature. Now a URL generated for one user's session won't validate against another. You could add a unique ID, hash, or UUID too - whatever you've got lying around that's unique enough to be validated (and can be exposed to the user). --- ***If you found this security tip useful?* 👍** [*Subscribe now*](#/portal/signup) *to get weekly* [***Security Tips***](https://securinglaravel.com/tag/tips/) *straight to your inbox, filled with practical, actionable advice to help you build safer apps.* ***Want to learn more?* 🤓** *Upgrade to a* [*Premium Subscription*](#/portal/signup) *for exclusive monthly* [**In Depth* articles*](https://securinglaravel.com/tag/in-depth/)*, or support my work with a* [*one-off tip*](#/portal/support)*! Your support directly funds my security work in the Laravel community.* 🥰 **Need a second set of eyes on your code?** *Book in a* [*Laravel Security Audit and Penetration Test*](https://stephenreescarter.net/laravel-security-audits-and-pentesting/?utm%5Fsource=securinglaravel.com) *today! I also offer budget-friendly* [*Security Reviews*](https://stephenreescarter.net/laravel-security-reviews/?utm%5Fsource=securinglaravel.com) *too.* *Finally, connect with me on* [*Bluesky*](https://bsky.app/profile/valorin.bsky.social?ref=securinglaravel.com)*, or* [*other socials*](https://pinkary.com/@valorin?ref=securinglaravel.com)*.* ### In Depth: Don't Trust Public Livewire Properties URL: https://securinglaravel.com/in-depth-dont-trust-public-livewire-properties/ Last updated: 2026-04-18T04:39:36.000Z Let's take a look at a rather fun (quite common) weakness associated with [Laravel Livewire](https://livewire.laravel.com/?ref=securinglaravel.com) \- **manipulating public properties**. In Livewire, public properties are synced between the server and browser, allowing both sides to access and manipulate them as needed. However, because they are defined as standard PHP public properties, it's incredibly easy for us as developers to think of them as such, even though they are no longer trusted or safe. Herein lies the weakness to be exploited. Let's look at a really simple example: ```php #[Title('Title demo')] class TitleDemo extends Component { public string $title = 'Hello, world!'; public function render(): View { return view('livewire.title-demo'); } } ``` Title Demo Livewire Component Which looks like this in the browser: ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/04/image.png) Title demo app If I change the title in the text box, the title on the page changes too. That's Livewire working its magic with the `wire:model.live="title"` property defined on the input field. However, even if that input field is no longer present, we can still change the value on-demand, using the `Livewire` Javascript object... ```javascript Livewire.first().set('title', 'PWNED') ``` ![](https://storage.ghost.io/c/d9/0d/d90de76f-6031-4e2c-85b8-3447a38c4992/content/images/2026/04/image-2.png) Livewire property modified through the console. Now, we could leave it here, but that feels like a harmless exploit you have some fun with in your local browser. However, there are some significant implications that we really should explore to understand the full scope of this weakness - plus, we should have some fun in the process! 😈 _This post is for paying subscribers only._ ### Security Tip: Stop Putting Actions on GET Requests! URL: https://securinglaravel.com/security-tip-stop-putting-actions-on-get-requests/ Last updated: 2026-03-17T05:33:42.000Z 💡 **Note, I'll just be using* *`POST`* *in this article to keep things simple, but I am talking about all four:* *`POST`* *,* *`PUT`* *,* *`PATCH`* *, and* `DELETE`**.* Let me ask you a question: **What is the difference between `GET` and `POST`?** You're probably thinking something along the lines of: *`GET` requests retrieve data from the server, while `POST` requests send data to the server and perform actions. Links use `GET` requests and forms use `POST` requests.* But what about this question: **What is the technical difference between `GET` and `POST`?** Similar to the above, you're probably thinking: `GET` *requests can be made entirely from a URL and triggered through links, while* `POST` *requests need to be submitted through a form submission, Javascript, or an API request.* While the first answer talks about the flow of data (`GET` \- retrieve, `POST` \- store) and notes that `POST` requests can perform actions, the second answer makes no mention of actions or security in general. Which brings us to the final question: **What is the difference in security between `GET` and `POST`?** *`GET` requests can be triggered from unsafe contexts and should never be trusted to perform actions, while `POST` requests have a lot of protections provided by the browser so you can use them safely (mostly).* Since I think that deserves more of an explanation, let's look at all the ways `GET` and `POST` requests can be triggered maliciously **without** [**Cross-Site Scripting (XSS)**](https://securinglaravel.com/tag/xss/): `GET` requests: 1. Can be triggered by third-party sites through links (``) that the victim clicks on. 2. Can be triggered by third-party sites through resources (``, `