Back to Blog
high SEVERITY2 min read

How a writable root filesystem service vulnerability happens in Docker Compose and how to fix it

The `opensearch` service defined in `Docker/LocalCluster.s3.yaml` was running with a fully writable root filesystem, giving any process inside the container the ability to modify binaries, drop payloads, or persist malicious changes. The fix pins the container to a read-only root filesystem with `read_only: true`, closing off a common post-compromise persistence and tampering vector while preserving normal OpenSearch operation.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

This is a Docker Compose security misconfiguration (CWE-732, Incorrect Permission Assignment for Critical Resource) where the `opensearch` service in `Docker/LocalCluster.s3.yaml` had a writable root filesystem, letting a compromised process modify container files or drop payloads. The fix adds `read_only: true` to the service definition, forcing any legitimate temporary writes to go through explicit `tmpfs` mounts or named volumes instead of the container's root filesystem.

Vulnerability at a Glance

cweCWE-732 (Incorrect Permission Assignment for Critical Resource)
fixAdd `read_only: true` to the service and mount `tmpfs`/named volumes for the specific paths OpenSearch legitimately needs to write to
riskContainer compromise leads to persistent payload storage, binary tampering, or malware download inside the running container
languageYAML / Docker Compose
root causeThe `opensearch` service definition in `Docker/LocalCluster.s3.yaml` omitted `read_only: true`, leaving the entire container filesystem writable by default
vulnerabilityWritable root filesystem in a Docker Compose service (opensearch)

Introduction

The Docker/LocalCluster.s3.yaml file defines the local development cluster used to spin up supporting services — including opensearch, which backs search and logging functionality for the stack. But a flaw in how that service was configured created a security risk: the opensearch container was allowed to run with a fully writable root filesystem, with no read_only restriction in place.

That might sound like a minor detail, but for anyone who has debugged a container compromise, it's a big deal. A writable root filesystem means that if an attacker ever gets code execution inside the opensearch container — through a vulnerable plugin, a deserialization bug, or a supply-chain compromise in a dependency — they don't just get a foothold, they get a durable one. They can drop tools, rewrite startup scripts, or tamper with application files, and those changes can persist for the life of the container.

The Vulnerability Explained

Semgrep's yaml.docker-compose.security.writable-filesystem-service rule flagged this exact pattern in Docker/LocalCluster.s3.yaml:

services:
  opensearch:
    image: opensearchproject/opensearch:2.11.0
    container_name: opensearch
    environment:
      - discovery.type=single-node
    ports:
      - "9200:9200"
    volumes:
      - opensearch-data:/usr/share/opensearch/data

Notice what's missing here: there is no read_only key at all. By default, Docker Compose services run with a fully writable filesystem unless you explicitly lock it down. That means every directory inside the container — /bin, /etc, /usr/share/opensearch, temp directories, everything — is writable by the OpenSearch process at runtime.

Why this matters in practice: OpenSearch, like most JVM-based services, exposes a large attack surface — REST APIs, plugin loading, script execution in some configurations, and JSON/YAML parsing of untrusted data. If any of that surface is ever exploited (for example, via a malicious query, a vulnerable plugin, or a poisoned dependency pulled in at build time), the attacker doesn't just get to read data — they get a container where they can:

  • Download and execute additional payloads (a second-stage backdoor, a cryptominer, a reverse shell binary).
  • Modify OpenSearch's own configuration or startup scripts so the compromise survives a container restart.
  • Tamper with logs or data files to cover their tracks.

Because the root filesystem is writable, none of this requires privilege escalation inside the container — the attacker already has write access to everything the OpenSearch process can reach.

The F

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #291

Related Articles

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.