When you’re building a microservices-based application, it’s understood that application programming interfaces (APIs) are what holds the whole thing together and makes it work. With more monolithic architectures, the APIs that are exposed might be less obvious, but if you have a web app, you almost certainly have some services running, meaning you have APIs that you should be testing for functionality and security. This post runs through a simple scenario to show how quickly APIs can proliferate in any architecture, quietly increasing your overall web attack surface – and how you can make sure you’re staying secure no matter what’s running under the hood.copyThis makes an operating system call to execute the resize.py script, giving it an input file and expecting a resized output file. While this works well enough for a prototype, the approach has its downsides:A more elegant solution would be to put the image manipulation functionality in its own web service and define an API for it. The service would listen for calls at a specified URL, accept an input image, and return the resized image. It can run on the same server or somewhere entirely different, and you might use it by simply sending the input image to https://your-server-name/api/resize. This addresses most of the disadvantages of a simple local In some cases, going the API route may be preferable for external reasons. For example, it could be that executing operating system commands using functions like
Hidden architectural decisions: simple script or simple API?
One typical situation is where you need to deal with a separate process for doing some server-side operation. Say you have a full-stack JavaScript app with Node.js on the server that needs image manipulation functionality in one place, maybe to automatically generate image thumbnails. You could do this all in JavaScript but decide to use an existing Python script for performance and convenience. So as you’re prototyping your app, the simplest way is to call:imgData=exec('python3 resize.py "input-file.png" "output-file.png"')
- Risk of command injection: If a malicious user is able to control the input or output file name and this data is not sanitized, the application could be vulnerable to OS command injection. In the worst-case scenario, an attacker may be able to execute operating system commands on your web server.
- Scalability and performance issues: The script runs on the same server as the main application, which could lead to performance issues in a high-load production environment, especially when handling concurrent requests.
- Limited access control: While likely not an issue for a small script, there is no easy way to control access to the resize operation itself or set rate limits. For a more complex script or executable with multiple operations and parameters, the only way to control access to each operation would be in the application logic.
exec():- There’s no direct risk of command injection since you’re not passing user input directly to a local script on the server (though you’re opening up a whole other can of security worms – more on that later).
- Easy to adapt, reuse and modify – once you have the API endpoint defined, how you implement the required operations is entirely up to you. The same endpoint can be used whether you have a single-purpose Python script behind it, decide to change the underlying technology or maybe set up a multi-purpose image manipulation service that adds more endpoints and features.
- The service is scalable independently of the main application. Depending on the load and business needs, you could run it on a single on-prem server, spread it across multiple containers in the public cloud or choose anything in between.
- You can define fine-grained access control and rate limiting for each API endpoint, setting up authorization, auditing, and logging as required.
exec() is forbidden by security policy or simply disabled. Also, with more service-oriented application architectures, adding another service might simply be the natural thing to do.




