Every item is read by a person before it publishes. They are not auditing your whole codebase, but there is a set of things checked on every submission, and these fail more items than anything else.
Queries built by concatenation
A query assembled from request input with string concatenation is the single most common rejection. Prepared statements or a query builder, everywhere, without exceptions for the admin area. "Only an admin can reach it" is not an argument, because that assumes the admin check itself is perfect.
Output that is not escaped
Anything a user typed, printed back into a page without escaping. Comments, names, search terms, error messages that echo the input. Escape at the point of output rather than trying to clean at the point of input, which always misses a route.
Uploads that trust the file
- Checking the extension but not what the file actually is
- Writing uploads into a directory the web server will execute from
- Keeping the name the browser supplied, path separators and all
Secrets in the package
API keys, database passwords, a .env you forgot to exclude, your own test credentials. Search your archive for your email address before uploading. This one is embarrassing rather than subtle, and it happens constantly.
Obfuscated blocks
Encoded or minified server-side code with no explanation is rejected on sight. Not because it is necessarily malicious, but because it cannot be reviewed, and shipping unreviewable code to buyers is not something we will do.
Admin routes without a check
An admin page that relies on nobody guessing the URL. Every privileged route needs its own authorisation check, not just an absent link in the menu.