Part 2 of 3 in the E-Commerce Security series. Part 1 covers the fundamentals. This post goes deeper.
I recently published a security checklist for PMs and mid-level developers working on e-commerce platforms. Checklists are great. They tell you what to do. But if you've been building and securing production systems for a while, you already know the "what." The harder question is "how" -- how do you actually implement these things in a codebase that has real customers, legacy decisions, and a product team that needs features shipped yesterday?
This post is that conversation. If you've read Part 1, you know my background. The thing that struck me most during that journey - from building stores to working at a cybersecurity company - was the gap between security literature and security practice. Academic papers will tell you about threat models. Blog posts will tell you to use HTTPS. Neither will tell you how to roll out a Content Security Policy on a checkout page that loads eleven third-party scripts without breaking everything on a Friday afternoon.
So this is the practical middle ground. I'm writing for senior developers, tech leads, and security engineers who are already past the basics. I'll assume you know what XSS is. I won't explain what a JWT is. Instead, I want to talk about the specific, gnarly problems that come up when building and securing e-commerce systems, and the approaches that actually work.
1. The British Airways Problem: Third-Party Scripts in Your Checkout
If there's one section of this post I'd ask you to take seriously, it's this one.
In 2018, British Airways got hit with one of the most elegant attacks I've studied. The Magecart group compromised a third-party JavaScript file that loaded on BA's payment page. The malicious code was roughly 22 lines -- it hooked into the submit event on the checkout form, copied the payment details, and sent them to a lookalike domain (baways.com, if I remember correctly). Around 380,000 customers had their card details stolen. The ICO initially proposed a 183 million pound fine.
The thing that keeps me up at night about this attack isn't its sophistication. It's how ordinary it is. Every e-commerce site I've ever worked on loads third-party JavaScript on pages that handle sensitive data. Analytics, A/B testing tools, chat widgets, marketing pixels, affiliate trackers. Every single one of those scripts has full access to the DOM, including form fields where customers type their card numbers.
Your security is only as good as every script running on your page, and you probably don't control most of them.
From a backend perspective, there are two things you can do about this: Content Security Policy (CSP) and Subresource Integrity (SRI).
CSP is a response header your server sends that declares which sources of scripts, styles, and other resources the browser should accept. Set it on your backend and anything not in your policy gets blocked.
SRI lets you attach a cryptographic hash to script tags so that if a file is tampered with (like in the BA attack), the browser refuses to execute it. Both are configured and served from your backend, even though they enforce behaviour in the browser.
The practical takeaway: work with your frontend and web security teams (if you have one) to audit every third-party script on checkout and payment pages. For each one, ask three questions. Does this need to be on this page? Can it be moved to a non-sensitive page? Can it be self-hosted? Then set your CSP headers accordingly. Start with Content-Security-Policy-Report-Only to catalogue violations before enforcing, and tighten iteratively.
2. API Security for Headless Commerce
Most e-commerce platforms I work with now are API-driven. Headless storefronts, mobile apps, third-party integrations pulling product data, warehouse systems pushing inventory updates. The traditional model where security meant protecting a monolithic web app is long gone. Your API surface is your attack surface, and it's probably larger than you think.
Authentication needs to be layered. For customer-facing APIs, OAuth 2.0 with OpenID Connect is the standard I'd push for. Use the authorization code flow with PKCE for SPAs and mobile apps -- the implicit flow is deprecated for good reason. For service-to-service integrations with partners, scoped API keys with clearly defined permissions work, but treat them like credentials, not configuration. And everywhere, prefer short-lived tokens. It's not uncommon to find e-commerce platforms issuing access tokens valid for 30 days. That's 30 days of exposure if one gets leaked. Fifteen minutes with a refresh token mechanism is a much better posture.
Rate limiting is something every team claims to have, but most implementations only cover the obvious case: brute-force login attempts. That matters, but in e-commerce, rate limiting serves other purposes too. Competitors will scrape your prices. Bots will enumerate your inventory to figure out stock levels before a limited drop. Attackers will probe coupon code endpoints to find valid codes by brute force. Your rate limiting strategy needs to account for all of this. Tools like Kong, nginx rate limiting modules, or cloud-native options like AWS WAF give you the mechanics, but the thinking -- which endpoints, what thresholds, what response -- that's on you and your team.
Input validation deserves special attention at the points where e-commerce backends are most vulnerable. Search is a big one -- if you're passing user search terms into database queries or Elasticsearch, that's a classic injection surface. Address forms are another; SQL injection payloads in the "address line 2" field are more common than you'd think. Coupon code inputs get hammered. Filter parameters on product listing pages, especially when they translate directly to query parameters, are often overlooked. Validate and sanitise on the server side, always, regardless of what the frontend does. Use parameterised queries or prepared statements for all database interactions -- if you're using an ORM in Go (like GORM or sqlx) or PHP (like Doctrine or Eloquent), you get this largely for free, but raw queries are still common in performance-critical paths like inventory checks and order processing, and that's where injection slips in.
If you're running GraphQL, you've got a few extra considerations. Disable introspection in production -- there's no reason to let anyone discover your entire schema. Implement query depth limiting so an attacker can't craft a deeply nested query that tanks your server. And consider query cost analysis: not all queries are equal, and a single GraphQL request can be as expensive as hundreds of REST calls if you're not careful. Most GraphQL server libraries have middleware or plugins for depth limiting and cost analysis, or you can enforce these constraints at the API gateway level.
3. Secrets, Config, and Environment Security
Secrets end up in the wrong places through entirely predictable paths, and it happens more often than anyone in the industry likes to admit. Developers hardcode API keys during local development and forget to remove them. The .env file gets committed because someone forgot to add it to .gitignore (or added it after the first commit). CI/CD logs print environment variables during debugging and nobody cleans them up. Terraform state files contain secrets in plaintext and get stored in an S3 bucket with overly permissive access.
The fix isn't just "use a secrets manager," though you should. HashiCorp Vault, AWS Secrets Manager, Google Cloud Secret Manager, Azure Key Vault -- any of these will do. The question is the workflow. For e-commerce, the critical scenario is rotating payment gateway credentials without downtime. A solid pattern for this: store credentials in your secrets manager with versioning, have your application read from the manager at startup (or on a short refresh interval), issue new credentials with the provider, update the secret in the manager, and let running instances pick up the new value. For payment gateways like Stripe or Adyen that support multiple active API keys, you can have both old and new keys active simultaneously during the transition. Test this in staging before you need to do it in an emergency.
On the preventive side, tools like git-secrets can scan your commit history and pre-commit hooks for patterns that look like credentials. Add them to your pre-commit hooks and your CI pipeline. They're not perfect -- they catch patterns, not intent -- but they catch the obvious mistakes before they reach production.
Environment parity is the other half of this. It's a widespread practice in the industry for e-commerce companies to copy production databases to staging so developers can work with "realistic" data. That realistic data includes customer names, email addresses, physical addresses, and order histories. If staging has weaker access controls than production (and it almost always does), you've just created a much easier path to a data breach. Implement data anonymisation for non-production environments. Tools like PostgreSQL Anonymizer or custom scripts that replace PII with realistic but fake data let your team work with representative datasets without the liability. It takes effort to set up, but the alternative is explaining to a regulator why customer data was accessible from an environment with shared passwords.
4. Dependency and Supply Chain Security
Running a dependency scanner is the starting point, not the finish line. Most teams run some form of dependency scanning. Far fewer have a coherent strategy for what to do with the results.
Here's the reality: a dependency audit on a mature e-commerce project will often return dozens or hundreds of findings, many of which are transitive dependencies you can't directly control, and some of which have no available fix. Triaging these is the actual work. Separate findings by whether they're in direct vs. transitive dependencies, whether the vulnerable code path is reachable in your application, and whether the severity is relevant to your context (a regex denial-of-service in a server-side dependency matters more than one in a dev-only build tool).
Lock files deserve more attention than they get. Whether it's package-lock.json, yarn.lock, Gemfile.lock, or poetry.lock - your lock file is a security-critical file. It pins the exact versions of every dependency in your tree. If someone modifies it in a PR, that change deserves the same scrutiny as a code change. Lock file diffs should be reviewed. It takes two minutes and it's one of the cheapest security measures you can implement.
Supply chain attacks are a real and active threat, and they're getting bigger. In March 2026, Axios - one of the most widely-used packages on npm with over 70 million weekly downloads - was compromised when a North Korea-linked threat actor gained control of a maintainer's account and published backdoored versions that delivered a remote access trojan. The malicious versions were live for only two to three hours, but because they were tagged as latest, any npm install during that window pulled compromised code. Earlier incidents followed similar patterns: ua-parser-js in 2021 (cryptominer injected via a hijacked maintainer account) and event-stream in 2018 (a new maintainer granted access who eventually injected malicious code). Beyond account takeovers, typosquatting remains a problem - attackers publishing packages with names like crossenv instead of cross-env. These aren't theoretical risks. They're escalating.
For automated dependency updates, tools like Renovate or Dependabot handle the mechanics. The strategy matters more than the tool. Auto-merge patch updates that pass your test suite -- these are almost always safe. Create PRs for minor updates and review them within a sprint. Flag major updates for manual review and dedicated testing. This keeps your dependencies current without overwhelming your team with PRs or letting updates pile up for months.
If you're deploying with Docker, scan your base images with tools like Trivy or Grype. Use minimal base images (distroless or Alpine) to reduce the attack surface. Run your application as a non-root user inside the container. These are table stakes, but I still encounter production e-commerce containers running as root on a full Ubuntu base image more often than I'd like.
5. Logging and Monitoring That Actually Works
Logging is one of those areas where the gap between "we have logging" and "our logging would actually help us detect and respond to a breach" is enormous.
What to log in e-commerce: every authentication event (login, logout, failed login, password reset request), every permission change (user promoted to admin, new API key issued), every significant action in the payment flow (checkout initiated, payment attempted, payment succeeded, payment failed, refund processed), and every admin action (product price changed, bulk export initiated, user account modified). These are your forensic breadcrumbs.
What not to log is equally important, and this is where I see teams get it wrong regularly. Do not log full credit card numbers, CVVs, or raw payment details -- obviously. But also don't log session tokens, full request bodies on authentication endpoints (which contain passwords), or unmasked PII like email addresses and physical addresses in contexts where they're not needed for debugging. Build your structured logging with a sanitisation layer that strips or masks sensitive fields by default. If you're using Monolog in PHP or zerolog/zap in Go, create a custom processor or hook that redacts fields matching patterns like "password," "token," "card," "cvv," and "authorization." It's easier to add specific fields to a log when you need them than to retroactively scrub sensitive data from a log store.
Alerting is where most monitoring setups fail in practice. The problem is almost never that teams lack alerts -- it's that they have too many, the signal-to-noise ratio is terrible, and everyone has learned to ignore them. For e-commerce specifically, these are the patterns worth alerting on: a spike in failed payment attempts (could indicate card testing), admin logins from unusual IP addresses or at unusual times, bulk data exports from the admin panel, sudden changes in the rate of coupon code applications (could indicate a leaked or brute-forced code), and any modification to checkout page scripts or CSP headers.
For tooling, whether you're using the ELK stack, Datadog, Grafana with Loki, or CloudWatch, the principles are the same: structured logs (JSON, not free text), centralised collection, retention policies that balance compliance requirements with cost, and dashboards that someone actually looks at. The best monitoring setups tend to be simple. A single dashboard showing failed payment rate, error rate by endpoint, and active sessions. Nothing fancy. But when it's visible - on a screen in the engineering area, in a Slack channel, wherever your team actually looks - people notice when the numbers look wrong.
6. Secure CI/CD for E-Commerce
Your CI/CD pipeline can deploy code to production, access production secrets, and modify infrastructure. It is, functionally, the most privileged actor in your entire system. It's genuinely strange how many teams that obsess over application security treat their Jenkins, GitHub Actions, or GitLab CI configuration as an afterthought.
Pipeline security starts with how secrets are injected. Never put secrets in your pipeline configuration files. Use your CI platform's secret management (GitHub Actions secrets, GitLab CI variables marked as protected, etc.) and restrict which branches and workflows can access them. The service accounts your pipeline uses should follow least privilege -- your deployment pipeline doesn't need the ability to create new IAM users or modify security groups. Use separate service accounts for different pipeline stages if your platform supports it.
Require signed commits on your main branch and enforce branch protection rules. At minimum: require PR reviews before merging, prevent force pushes to main, and require status checks to pass. These aren't just process niceties -- they're security controls. If an attacker compromises a developer's account, branch protection is what prevents them from pushing a backdoor directly to production.
Pre-deployment gates should include dependency vulnerability scanning (Snyk, Trivy, or similar), static analysis (Semgrep is a strong option -- it's fast, has good rules for security patterns, and the custom rule syntax is reasonable), and container image scanning if you're deploying containers. Make these gates blocking, not advisory. If a critical vulnerability is detected, the deploy should fail. Yes, this will occasionally slow you down. That's the point.
For deployment strategy, blue-green or canary deployments aren't just about uptime -- they're about limiting blast radius. If a release is compromised (or just buggy in a security-relevant way), canary deployments mean only a fraction of your traffic is affected, and you can roll back before the damage spreads. For an e-commerce platform handling payment data, the ability to roll back a bad deployment in minutes rather than hours is a security control in its own right.
7. PCI DSS in Practice
PCI DSS compliance is one of those topics where the documentation is thorough but the practical guidance for developers is sparse. I'm not going to walk you through all twelve requirements. Instead, I want to focus on the engineering decisions that determine how painful PCI compliance is for your team day to day.
The most important decision is which SAQ (Self-Assessment Questionnaire) applies to you, because this determines your compliance burden. If you use a hosted payment page (like Stripe Checkout or Adyen's Drop-in) where card data never touches your servers, you're likely looking at SAQ A -- a short questionnaire with around 20 requirements. If you're accepting card data directly on your own page and passing it to a payment processor, you're in SAQ A-EP territory at minimum. If card data transits or is stored on your systems, you're looking at SAQ D, which covers all 300+ PCI DSS requirements and typically requires a Qualified Security Assessor. The difference between SAQ A and SAQ D is, in practical terms, the difference between a few days of documentation and a months-long audit engagement.
Tokenisation is how you stay on the easy side of that line. Use Stripe Elements, Adyen Web Components, Braintree's hosted fields, or a similar integration where the payment form fields are rendered in an iframe controlled by the payment provider. The card data goes directly from the customer's browser to the provider, your servers never see it, and you get back a token you can use for charges and refunds. This is something my team and I put into practice with the NordVPN and Mastercard partnership - the payment flow is handled through a trusted provider integration, keeping card data off NordVPN's servers entirely. It's the kind of approach that keeps you on the right side of PCI compliance. Teams often build custom payment forms because they want more control over the UX. Unless you have a very compelling reason and the budget for SAQ D compliance, don't do this.
Scope reduction is the broader principle. Every system that handles, processes, or can access cardholder data is in scope for PCI. Every system connected to those systems is potentially in scope. The architecture decisions you make -- network segmentation, separate databases for payment data, restricted access to payment service components -- directly determine how much of your infrastructure a PCI assessor needs to examine. When I'm designing a system, I think about PCI scope the way I think about blast radius: the smaller the better, and you achieve it through isolation
Wrapping Up
Security isn't a feature you ship and move on from. It's a property of how you build, deploy, and maintain your systems. Every decision I've described in this post -- from CSP headers to pipeline configuration to PCI scope reduction -- is something that needs ongoing attention. Dependencies get new vulnerabilities. Third-party scripts change. Team members rotate and institutional knowledge fades. The systems that stay secure are the ones where security is part of the engineering culture, not a quarterly checklist exercise.
That said, if your team is still building that foundation, start with the checklist. Part 1 lays out the essential security measures every e-commerce team should have in place. Get those right first. Everything in this post builds on top of them.
And if you want to understand why all of this matters from the other side of the screen -- the people actually entering their card numbers and home addresses into your checkout page -- Part 3 covers e-commerce security from the consumer's perspective. Because ultimately, that's who we're building this for.
This is Part 2 of a 3-part series on e-commerce security. Part 1: The Security Checklist | Part 2: You are here | Part 3: The One-Minute Safety Check Before You Buy Online