Skip to main content
Back to all articles
Cybersecurity
Jul 12, 2026
11 min read
2040 views

8 Security Gaps We Find in Almost Every Web Application We Audit

Across audits of business web applications, the same eight weaknesses appear again and again — and almost none of them are exotic. They are defaults nobody changed and checks nobody added. Here is each gap, how to test for it yourself, and how to prioritise the fixes.

ByteStackLab Team

Written by

ByteStackLab Team

The ByteStackLab engineering team — we design, build, and maintain AI automation systems, SaaS platforms, and custom web and mobile software for businesses worldwide.

8 Security Gaps We Find in Almost Every Web Application We Audit

Featured cover

8 Security Gaps We Find in Almost Every Web Application We Audit

Application security audits are less exotic than the phrase suggests. The same eight weaknesses turn up across business web applications with remarkable consistency, and almost none of them involve clever attacks. They are defaults nobody changed, checks nobody added, and assumptions nobody revisited after launch.

Here is each one, how to check for it in your own application, and how to decide what to fix first.

1. Broken access control

The most common serious finding, and the one that leaks the most data. The application verifies who you are and then forgets to verify whether this record is yours.

The classic shape: a URL like /invoices/1042. Change it to 1043 and you see somebody else's invoice. The same gap appears in API endpoints, file downloads, and any admin function protected only by the absence of a link in the navigation.

How to check: create two ordinary accounts. Log in as the first, note the identifiers in URLs and API responses, then log in as the second and request the first account's resources directly. Anything that returns data instead of a rejection is a finding. Repeat for every write operation — update and delete are frequently less protected than read.

The fix: authorise on every request, at the data layer rather than in the controller, so the default query can only return records the current user may see.

2. Secrets in the repository and debug mode in production

Database passwords, API keys, and payment credentials committed to version control, and framework debug mode left enabled on the live server.

Debug mode is worth dwelling on because it is so common and so damaging. On many frameworks, an unhandled error in debug mode renders a page containing the stack trace, the source code around the failure, and frequently the full environment configuration — database credentials included. An attacker does not need to find a vulnerability; they need to find an error.

How to check: trigger an error on your production site by requesting a malformed URL or a nonexistent record. If you see anything other than a plain branded error page, that is a critical finding. Then search your repository history for committed credentials — history, not just the current files, since deleting a secret in a later commit does not remove it.

The fix: debug off and error reporting to logs only in production; secrets in environment variables or a secrets manager, never committed. Any credential that has ever been committed must be rotated — assume it is compromised.

3. Uploaded files stored where anyone can read them

Applications accept CVs, identity documents, invoices, and medical forms, then write them to a publicly served directory with a predictable name. No authentication is required to retrieve them; only the URL.

Job application systems are the recurring offender. A career portal collecting résumés is collecting names, phone numbers, addresses, and employment histories — a rich set of personal data, frequently stored on the public disk because that was the framework's default in the tutorial.

How to check: upload a file through your application, find its resulting URL, then open it in a private browser window with no session. If it loads, every file in that directory is public.

The fix: store sensitive uploads on a private disk and serve them through a route that authenticates and authorises the requester. Use non-guessable filenames as defence in depth, not as the primary control.

4. No rate limiting where it matters

Login, password reset, and public write endpoints — contact forms, newsletter signup, registration — accepting unlimited requests. This enables credential stuffing against your login, and turns your contact form into a spam relay and your transactional email reputation into a casualty.

How to check: submit your login form twenty times in quick succession with a wrong password. If the twentieth attempt behaves exactly like the first, there is no limit.

The fix: per-IP and per-account limits on authentication endpoints, with progressive delays after repeated failures. Separate, tighter limits on public write endpoints. A honeypot field catches a large share of automated form submissions at essentially no cost to real users.

5. Dependencies nobody has updated

Modern applications run on hundreds of third-party packages. Vulnerabilities in popular ones are published openly, along with working exploit details — which means the window between disclosure and opportunistic scanning is short.

How to check: run your package manager's audit command. Most ecosystems have one built in. Then check when your framework version stops receiving security patches.

The fix: automated dependency scanning in CI, a scheduled monthly patch window, and a policy of staying on supported major versions. Skipping upgrades does not avoid the work; it accumulates it into a single much harder migration later.

6. Missing security headers and permissive CORS

A handful of response headers meaningfully reduce the impact of several attack classes, and most applications ship without them: a content security policy, HSTS, X-Content-Type-Options, and a sensible referrer policy.

CORS deserves particular attention because the insecure configuration is the one people reach for when debugging. A wildcard origin, or an origin reflected back from the request, undoes the browser protection it is meant to configure.

How to check: inspect your response headers in browser dev tools, or use any of the free header-scanning services. For CORS, send a request with an arbitrary Origin header and see whether it is echoed in the response.

The fix: an explicit allowlist of origins, never a wildcard on any endpoint that carries credentials. Add the headers at the reverse proxy or framework middleware layer so they apply everywhere by default.

7. Error messages that explain too much

Two related patterns. First, raw exception messages returned in API responses, revealing table names, query structure, and file paths. Second, authentication flows that distinguish "no such account" from "wrong password," which lets an attacker enumerate valid accounts before attempting anything else. Registration and password reset flows leak the same way.

How to check: submit deliberately invalid data to your API and read the response body. Then attempt a login with an address you know does not exist, and compare it against a real address with a wrong password. Different responses are a finding.

The fix: log the detail, return a generic message. Keep authentication responses identical regardless of which part failed, including the response time where practical.

8. Nobody is watching

The gap that turns a small incident into a large one. Applications with no security logging cannot answer the questions that matter after a breach: when did this start, what was accessed, is it still happening.

How to check: ask whether you could currently answer "did anyone access this customer's records last Tuesday?" If not, the logging does not exist.

The fix: log authentication successes and failures, permission denials, administrative actions, and access to sensitive records — with user, timestamp, and source address, stored somewhere an attacker with application access cannot edit. Alert on the patterns that matter: repeated failed logins, privilege changes, unusual bulk data access.

Fixing them in a sensible order

With a finite budget, rank by exploitability multiplied by blast radius rather than by severity label alone.

  1. Anything exposing data without authentication. Public file storage, debug mode, unauthenticated endpoints. No skill required to exploit, maximum consequence.
  2. Broken access control on sensitive records. Requires an account, and accounts are usually easy to obtain.
  3. Committed credentials. Rotate immediately; the disclosure may already have happened.
  4. Missing rate limits on authentication. Enables the attack that leads to everything above.
  5. Known-vulnerable dependencies, prioritised by whether the vulnerable code path is one your application actually reaches.
  6. Headers, error messages, logging. Important, but they reduce impact rather than close a door standing open.

Most breaches of business applications are not sophisticated. They are automated scans finding an unchanged default. Closing the ordinary gaps removes most of the realistic risk.

What a real audit produces

A useful security assessment is not a scanner report forwarded as a PDF. It is a review of authentication and authorisation logic against your actual roles, manual testing of access control across real user accounts, a review of data storage and third-party integrations, a dependency and configuration check — and, most importantly, findings written so your developers can act on them: what is wrong, where, why it matters, and what the fix looks like in your codebase.

That is deliberately how we structure our Cybersecurity & Application Security service: a prioritised fix plan your team can actually ship, not a document that makes everyone anxious and nobody effective.

Tags:

#cybersecurity #application security #vulnerability assessment #OWASP #secure development #security audit

Share this article:

Ready to Implement This Strategy?

If this article matches your current challenge, we can help you design, build, and launch a tailored solution faster.