Course contentsShow
← Linux
Lesson 980 of 1,8199. Security Hardening and Mandatory Access ControlFree lesson

Securing Interpreted Languages

Write specific profiles for script runners like Python or Node.js to restrict runtime capabilities.

In this lesson, you will learn how to use AppArmor to constrain the runtime behavior of interpreted languages like Python or Node.js to prevent them from becoming vectors for arbitrary code execution.

The Challenge of Interpreted Runtimes

Interpreted languages are dynamic by nature; an application might load libraries or execute sub-processes based on user input at runtime. If you apply a generic profile to python3 or node, you often over-privilege the process, giving it access to system libraries or paths that a specific script has no business touching. To secure these, we must create granular profiles that define exactly what the interpreter is allowed to do when running a specific task.

Restricting the Interpreter

The goal is to move beyond the generic binary path and define a restricted sandbox. Instead of allowing python3 full access to the file system, we create a profile that limits the interpreter to only the necessary runtime libraries and specific data directories.

Consider a Python data-processing script. We don't want it accessing /etc/ or spawning a shell. A focused profile looks like this:

profile python-data-processor /usr/bin/python3.10 {
  # Include basic abstractions for the interpreter
  #include <abstractions/base>
  #include <abstractions/python>

  # Allow access only to the necessary data directory
  /var/lib/data-processor/ rw,
  /var/lib/data-processor/** r,

  # Deny the ability to execute shells or network tools
  deny /bin/sh x,
  deny /usr/bin/curl x,
  
  # Limit system capabilities
  deny capability sys_admin,
}

Best Practices for Hardening

When writing these profiles, use the audit flag while testing to identify exactly which files the interpreter attempts to load. Interpreters often require read access to many shared libraries in /usr/lib/; use abstractions/python or abstractions/nodejs to handle these standard requirements, then manually audit the specific file system paths your application script requires. By explicitly denying execution of common system binaries (/bin/sh, /bin/bash), you effectively neutralize the impact if an attacker manages to inject malicious logic into your script.

Key Takeaway: Restricting interpreted languages requires balancing the interpreter’s need for runtime libraries with strict filesystem path limitations and the explicit denial of execution rights for system shells.