Skip to content

Security: cainky/logtap

SECURITY.md

Security

This document describes logtap's security model and threat mitigations.

Supported Versions

Version Supported
0.4.x
< 0.4

Path Traversal Prevention

logtap exposes log file access over HTTP. The primary security concern is path traversal attacks where an attacker attempts to read files outside the configured log directory.

Threat Model

Attacker goal: Read arbitrary files on the server (e.g., /etc/passwd, ~/.ssh/id_rsa)

Attack vectors:

  • Directory traversal: ../../etc/passwd
  • Absolute paths: /etc/passwd
  • Mixed separators: ..\\..\\etc\\passwd
  • Symlink escape: Create logs/link -> /etc, request link/passwd
  • Null byte injection: file.log\x00../../etc/passwd
  • Path prefix collision: base /var/log, request resolves to /var/logs/evil
  • URL encoding: %2e%2e%2f (decoded ../)

Prevention Model

All file access goes through resolve_safe_path() in src/logtap/core/validation.py:

1. Input validation
   - Reject empty filenames
   - Reject NUL bytes (\x00)
   - Reject control characters (0x00-0x1F, 0x7F)
   - Reject path traversal sequences (..)
   - Reject path separators (/ and \)
   - Reject absolute paths (Unix: /..., Windows: C:\...)

2. Path construction
   - base_resolved = os.path.realpath(base_dir)
   - filepath = os.path.join(base_resolved, filename)
   - filepath_resolved = os.path.realpath(filepath)

3. Containment check
   - common = os.path.commonpath([base_resolved, filepath_resolved])
   - Verify: common == base_resolved
   - This handles symlink escape and prefix collisions

4. File type check (optional)
   - Verify target is a regular file (not directory, device, etc.)

Why commonpath instead of startswith

startswith is not reliable for path containment:

# WRONG - prefix collision
base = "/var/log"
candidate = "/var/logs/evil"
candidate.startswith(base)  # True! But /var/logs is different directory

commonpath correctly handles this:

# CORRECT
os.path.commonpath(["/var/log", "/var/logs/evil"])  # Returns "/var"
# "/var" != "/var/log" -> rejected

Symlink Handling

os.path.realpath() resolves all symlinks before the containment check:

base_dir/
  ok.log
  escape -> /etc/   (symlink to /etc)

Request: "escape/passwd"
1. Join: /base_dir/escape/passwd
2. Resolve: /etc/passwd (symlink followed)
3. Containment: commonpath(["/base_dir", "/etc/passwd"]) = "/"
4. "/" != "/base_dir" -> REJECTED

Invariants

The resolve_safe_path function guarantees:

  1. Return value is always None or an absolute canonical path
  2. If non-None, the path is within base_dir (symlink-safe)
  3. If require_exists=True, the path exists and is a regular file
  4. No user input can escape the base directory

Entry Points

All HTTP endpoints that access files use resolve_safe_path:

Endpoint Handler
GET /logs get_filepath() -> resolve_safe_path()
GET /logs/multi resolve_safe_path()
WS /logs/stream resolve_safe_path()
GET /logs/sse get_filepath() -> resolve_safe_path()
GET /parsed resolve_safe_path()

Rejected Patterns

Input Rejection Reason
. Special directory entry
.. Special directory entry
../etc/passwd Contains path separator
/etc/passwd Absolute path
foo/bar Contains path separator
foo\bar Contains path separator
file\x00.log Contains NUL byte
file\x1f.log Contains control char
C:\Windows Windows drive letter
(empty string) Empty filename

Allowed Patterns

Input Notes
my..log Allowed - ".." not a path component
v1..bak Allowed - ".." not a path component
file.log Normal filename

Additional Security Measures

API Key Authentication

Optional API key authentication via X-API-Key header or api_key query parameter.

logtap collect --api-key secret
logtap tail run1 --api-key secret

Regex DoS Protection

Uses google-re2 for regex operations, which guarantees linear-time matching and prevents ReDoS attacks.

Input Size Limits

  • Search term: max 100 characters
  • Lines per request: max 1000
  • Multi-file query: max 10 files

Reporting a Vulnerability

We take the security of logtap seriously. If you find a vulnerability:

  1. Do not open a public GitHub issue
  2. Send a direct message to the project maintainers or use GitHub's private vulnerability reporting
  3. Provide detailed information about the vulnerability
  4. Allow time for us to address it before public disclosure

We appreciate your help in keeping logtap and its users secure!

There aren’t any published security advisories