Skip to content

LDM Security Posture & Disclosures

Liferay Docker Manager (LDM) is designed strictly for local development and demonstration (sandbox) use. To provide a professional, platform-agnostic experience across macOS, Windows, and Linux, certain intentional security tradeoffs have been made.

1. Intentional Network Bindings (0.0.0.0)

LDM binds certain infrastructure components to 0.0.0.0 (all interfaces) instead of 127.0.0.1 (localhost) in specific scenarios:

  • macOS Multi-IP Loopback: On macOS (Silicon and Intel), Traefik is bound to 0.0.0.0 to ensure that custom hostnames (e.g., mysite.local) correctly route back to the Docker containers.
  • Gogo Shell Exposure: To allow telnet access to the Liferay OSGi console, the Gogo shell listener is bound to 0.0.0.0 inside the container network.
  • Wildcard Port Verification: When checking if a port is available on the host (in check_port), LDM binds a test socket to the address it was asked about -- 0.0.0.0 for the shared infrastructure ports, so that a conflict on any local interface is caught rather than only the one LDM happens to probe. This prevents port allocation conflicts when starting proxy and database containers. The bind test alone is not sufficient, and previously this entry claimed a guarantee it does not have: Windows permits a bind to 127.0.0.1:P to succeed while another socket holds 0.0.0.0:P, which is exactly how Docker Desktop publishes a container port. LDM-#1417 therefore added a connect probe that runs before the bind and treats a reachable listener as conclusive. That probe makes an outbound connection to the port under test and closes it immediately -- it never listens, so it adds no exposure of its own.

Mitigation: These bindings are only active while the LDM stack is running. In a standard home/office network, this exposure is limited to the local subnet. Users requiring stricter isolation should use a firewall or VPN.

2. Ignored Security Scans (Bandit/Audit Disclosures)

The LDM CI pipeline runs Bandit security scans. We explicitly ignore the following warnings to maintain core functionality:

Code Intent & Disclosure
B103 Permissive permissions. Used in cmd_upgrade to ensure the newly downloaded binary is executable, and -- separately and more broadly -- to make container-shared bind mounts writable by the uid the container runs as. (This entry previously also credited migrate_layout; c91c5123 removed the chmod from that method in May 2026, so it no longer sets permissions at all -- see LDM-#1917.) See Bind-Mount Permissions below for why the latter is 777 and where. Mitigation: All calls are wrapped in try...except and LDM prioritizes standard user ownership.
B104 Hardcoded bind to all interfaces. Required for macOS loopback, Gogo shell access, host port availability checks (especially on Windows WSL2/Hyper-V), and infrastructure setup.
B105 Hardcoded passwords or tokens. Used for default local development database passwords (e.g., test) and transient mount verification tokens. Mitigation: These are only used in isolated sandbox environments and are not intended for production secrets.
B108 Hardcoded /tmp directory. Used only for transient mount verification tokens.
B110 / B112 try...except pass/continue patterns. Used in loops (e.g., tag discovery, project scanning) and cleanup routines where a single failure should not abort the entire operation.
B202 Tar extractall operations. Used for snapshots and samples. Mitigation: LDM uses a mandatory is_within_root validation before every extraction to prevent Zip Slip / Path Traversal attacks.
B324 Use of MD5 hashing. Used in cloud-fetch for ETag verification and non-cryptographic file integrity checks.
B602 / B603 Subprocess execution with shell or without absolute paths. Used for complex piping during database snapshots, Windows bridge logic, and rendering the local man page (cmd_man). Mitigation: Commands are hardcoded or constructed from strictly sanitized internal identifiers.
B604 Function call with shell=True. Used in is_completion_enabled, cmd_completion, and cmd_man to interact with shell builtins and render the local man page. Mitigation: All command strings are hardcoded and contain no user-supplied input.
B605 Start process with a shell. Used in cmd_log_level to pipe Gogo shell commands via nc for dynamic log adjustment. Mitigation: The command string is hardcoded and only sanitized level/category strings are interpolated.
CVE-2026-4539 Pygments vulnerability. Ignored as LDM only uses Pygments for local console highlighting, posing no remote risk.

CodeQL Disclosures (GitHub code scanning)

CodeQL runs on GitHub's default setup, so there is no workflow or config file in this repository to carry path exclusions. Alerts that are correct as rules but wrong in context are dismissed with a reason, and recorded here so the dismissal is reviewable rather than invisible.

Alert Rule Location Disclosure
58 py/bind-socket-all-network-interfaces ldm_core/tests/test_base.py A test binds a real 0.0.0.0 listener on an ephemeral port (0) inside a with block that closes it. The wildcard bind is the assertion, not an oversight: LDM-#1417 was check_port failing to see a 0.0.0.0 listener while probing 127.0.0.1 on Windows, so binding loopback here would delete the behaviour under test. The docstring records why it is deliberately unmocked -- "the bug lived in the gap between what the mocks assumed and what the OS actually does".

Suppressions are also marked inline with # codeql[<rule-id>] next to the code, so the justification travels with the line rather than living only in a dismissal on GitHub.

3. Bind-Mount Permissions on Container-Shared Trees

LDM applies chmod 777 to the project and infrastructure directories it bind-mounts into containers. This is deliberate, it is not a leftover, and LDM-#1507 audited every site that does it. The reasoning is recorded in full on reclaim_volume_permissions() in ldm_core/utils.py and restated in a comment at each call site; the summary is:

  • The containers LDM runs write to these trees as uid 1000 -- the official elasticsearch:8.x image (liferay-search-global, started with no --user), and the Liferay image's Tomcat/Equinox process.
  • The host user is not uid 1000. It is 501 on macOS and typically 1001 on Linux CI.
  • A bind mount performs no uid translation on native Linux. LDM's permission reclamation chowns to the host uid, so at 750 the container is locked out of a tree it must read and write. 770 and 775 do not help either: the container uid is not in the host user's group, so the "other" class is the only one both parties fall into.

This was established the hard way. LDM-#599 hardened the reclamation default from 777 to 750; that broke Elasticsearch and Liferay on native Linux, and LDM-#645 ("restore native linux cross-container mapping") re-widened the affected sites one release later. Tightening one of them again reproduces that regression. macOS and Windows hide the problem, because Docker Desktop's virtiofs/gRPC-FUSE mounts present files as the host user regardless of mode -- which is why LDM-#599 shipped before anyone noticed.

Scope: the widening is confined to per-project and per-infrastructure directories under the project root and ~/.ldm/infra, plus the Elasticsearch data and snapshot repositories. It is never applied to LDM's own configuration or credentials: ~/.ldm/config is 700 and the files inside it, including target tokens, are 600.

The per-directory detail differs by code path and this paragraph used to gloss it as "data, state, logs, deploy, osgi, files, configs", which read as a specification of behaviour that does not hold (LDM-#1942):

tree pre-boot snapshot reclaim
deploy, files, configs, logs, modules widened uid 1000, 777
marketplace not widened uid 1000, 777 (since LDM-#1941)
data, state widened host uid, 755 -- deliberately not 777, because LDM-388 showed 777 broke Elasticsearch on restore
routes widened not reclaimed
scripts not widened not reclaimed
osgi/client-extensions (cx), client-extensions (ce_dir) cx widened neither reclaimed

osgi in the old wording was a gloss over three different answers: osgi/state takes the host-uid 755 treatment, osgi/marketplace joined the 777 set only with LDM-#1941, and osgi/client-extensions is not reclaimed at all.

Whether the two client-extension trees should be reclaimed is open on LDM-#1942 and is deliberately not guessed at: one of them is the developer's own git-managed source, which this code would chown away.

Mitigation: 750 remains the default of reclaim_volume_permissions(), so any future caller with no container on the other side of the mount gets least privilege without asking. Widening is opt-in per call site and each opt-in carries a comment naming the container and uid it exists for. As stated at the top of this document, LDM is a local development tool; these directories hold sandbox project data on a developer machine, not production data.

4. Hardened Command & Data Processing

Following our commitment to local security, the following hardening measures are implemented:

  • Native Command Piping: Database restore and snapshot operations now use native OS-level process piping (stdin/stdout) instead of shell=True. This eliminates the risk of shell injection while maintaining performance for large database dumps.
  • XXE Protection: Liferay XML license parsing uses a regex-based extraction layer instead of a standard XML parser. This provides absolute immunity to XML External Entity (XXE) attacks.
  • Identifier Sanitization: All project IDs, container names, and environment identifiers are strictly sanitized to allow only alphanumeric characters, preventing malicious path or command injection via project metadata.
  • Snapshot Integrity Verification: LDM generates SHA-256 checksums for all project snapshots. During recovery or import, the tool validates the archive against this checksum to detect accidental corruption or accidental tampering. Note that this provides integrity (data validity) but not authenticity (proof of origin), as the checksum files are stored alongside the data in the local filesystem.

5. Sensitive Data Masking (Log Redaction)

LDM implements a proactive redaction layer to prevent the accidental logging of sensitive information in clear-text.

  • Automatic Redaction: Common sensitive keys (e.g., PASSWORD, SECRET, TOKEN, AUTH) are automatically masked with [REDACTED] in all command execution logs and verbose output.
  • Local Focus: While LDM is a sandbox tool and developers are responsible for their local environment, this measure ensures that even when sharing logs for troubleshooting, sensitive credentials remain protected.

6. SSL Trust (mkcert)

LDM uses mkcert to provide a "Green Lock" experience. This requires installing a local Certificate Authority (CA) on your machine.

  • Risk: If your private CA key is compromised, an attacker could spoof websites on your local machine.
  • Mitigation: LDM stores certificates in ~/liferay-docker-certs. Ensure this directory is kept private.

7. Sudo & Internalized Elevation

To protect the integrity of the application cache (~/.shiv) and ensure consistent file ownership, LDM prohibits being run with the sudo prefix.

The tool utilizes a "Just-in-Time" elevation strategy. It runs as your standard user and only invokes sudo internally for specific system-level operations:

  • ldm fix-hosts: Requests elevation to append entries to /etc/hosts.
  • ldm upgrade: Requests elevation to replace the binary in system paths like /usr/local/bin. Uses a cp + rm pattern to handle cross-device links during replacement. On Windows, it seamlessly requests administrative privileges via UAC prompts when replacing the binary. Supports optional --beta flag for opting into pre-releases.

This ensures that all project data and temporary files remain owned by your local user, preventing permission-related lockouts.

8. Secrets Prevention & Commit Gates

LDM integrates Yelp's detect-secrets scanner into its pre-commit hook suite to block credentials, API keys, and passwords from ever being committed.

  • Baseline Exceptions: Legacy false positives or mock credentials required for unit testing are documented in .secrets.baseline (containing hashed values of the ignored strings) so they do not block development.
  • Manual Scanning: To scan all tracked files in the workspace at any time:
.venv/bin/python3 -m pre_commit run detect-secrets --all-files
  • Updating Exceptions: If a new safe mock key is flagged as a false positive, append it to the baseline:
detect-secrets scan --baseline .secrets.baseline

๐Ÿ’ก Reusing Secrets Prevention in Other Projects

To implement equivalent commit-blocking gates in your other repositories:

Option A: Yelp's detect-secrets (Python/Baseline approach)

Excellent for projects that already use Python or where you want a baseline file to manage exceptions. Add this to your .pre-commit-config.yaml:

  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.5.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

Generate the baseline file initially via detect-secrets scan > .secrets.baseline and track it in git.

Option B: Gitleaks (Compiled Go Binary / Fingerprint approach)

A fast, single-binary alternative that requires no Python runtime. Add this to your .pre-commit-config.yaml:

  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.2
    hooks:
      - id: gitleaks

To ignore mock keys or false positives, create a .gitleaksignore file in the root of the project to catalog exceptions by fingerprint or commit hash:

# .gitleaksignore
# Add mock tokens or hashes here to prevent them from blocking commits.
# Format: [fingerprint_hash] OR [commit_hash]
f7e5b56e5e4e6e94fe5de5424e66fef84be863f385

LDM is not a production orchestrator. Never use LDM to host publicly accessible Liferay instances or sensitive production data.


Last Updated: 2026-09-25 | Last Reviewed: 2026-09-25