[UI] Fix path traversal vulnerability in bridge.py done coding:9 safety:10

← UI
SECURITY FIX: Bridge service has path traversal vulnerability. Line 32-33 checks if path starts with BASE before resolving symlinks/relative paths, allowing attackers to write to arbitrary locations via ../../ sequences. Service crashed attempting to write to /etc. Fix: use os.path.realpath() before validation. Add tests for common bypass techniques. ## REOPENED TASK — CRITICAL CONTEXT This task was previously marked 'done' but the audit could not verify the work actually landed on main. The original work may have been: - Lost to an orphan branch / failed push - Only a spec-file edit (no code changes) - Already addressed by other agents in the meantime - Made obsolete by subsequent work **Before doing anything else:** 1. **Re-evaluate the task in light of CURRENT main state.** Read the spec and the relevant files on origin/main NOW. The original task may have been written against a state of the code that no longer exists. 2. **Verify the task still advances SciDEX's aims.** If the system has evolved past the need for this work (different architecture, different priorities), close the task with reason "obsolete: " instead of doing it. 3. **Check if it's already done.** Run `git log --grep=''` and read the related commits. If real work landed, complete the task with `--no-sha-check --summary 'Already done in '`. 4. **Make sure your changes don't regress recent functionality.** Many agents have been working on this codebase. Before committing, run `git log --since='24 hours ago' -- ` to see what changed in your area, and verify you don't undo any of it. 5. **Stay scoped.** Only do what this specific task asks for. Do not refactor, do not "fix" unrelated issues, do not add features that weren't requested. Scope creep at this point is regression risk. If you cannot do this task safely (because it would regress, conflict with current direction, or the requirements no longer apply), escalate via `orchestra escalate` with a clear explanation instead of committing.

Completion Notes

Auto-completed by supervisor after successful deploy to main

Git Commits (8)

[Verify] Fix path traversal vulnerability in bridge.py — already resolved [task:58e83560-3321-4ac1-931b-2e18d31c1d86]2026-04-24
[UI] Restore bridge service entrypoint [task:58e83560-3321-4ac1-931b-2e18d31c1d86]2026-04-20
[UI] Harden bridge upload path validation [task:58e83560-3321-4ac1-931b-2e18d31c1d86]2026-04-20
[UI] Restore bridge service entrypoint [task:58e83560-3321-4ac1-931b-2e18d31c1d86]2026-04-20
[UI] Harden bridge upload path validation [task:58e83560-3321-4ac1-931b-2e18d31c1d86]2026-04-20
[UI] Harden bridge upload path containment [task:58e83560-3321-4ac1-931b-2e18d31c1d86]2026-04-20
[UI] Mark bridge security task complete [task:58e83560-3321-4ac1-931b-2e18d31c1d86]2026-04-02
[UI] Fix path traversal vulnerability in bridge.py [task:58e83560-3321-4ac1-931b-2e18d31c1d86]2026-04-02
Spec File

Goal

Fix critical path traversal vulnerability in bridge.py that allows attackers to write to arbitrary filesystem locations outside the intended BASE directory. The current validation checks if the path string starts with BASE before resolving relative paths, enabling bypass via ../../ sequences.

Root Cause

Lines 32-33 in bridge.py:

fp=os.path.join(BASE,body["path"].lstrip("/"))
if not fp.startswith(BASE):self.send_response(403);self.end_headers();return

Attack vector:

  • Attacker sends path: ../../etc/passwd
  • os.path.join("/home/ubuntu/scidex", "../../etc/passwd")"/home/ubuntu/scidex/../../etc/passwd"
  • String starts with BASE ("/home/ubuntu/scidex") ✓ passes check
  • But resolves to /home/etc/passwd when accessed
  • Evidence: Service crashed on 2026-04-02 00:39:38 with:

    PermissionError: [Errno 13] Permission denied: '/home/ubuntu/scidex/../../etc'

    Acceptance Criteria

    ☑ Spec file created
    ☑ Path validation uses realpath() to resolve symlinks and relative paths before check
    ☑ Security test script created with common bypass techniques
    ☑ All bypass attempts correctly blocked (403 response)
    ☑ Legitimate paths within BASE still work
    ☑ Bridge service restarted and operational
    ☑ No permission errors in service logs
    ☑ Commit and push fix

    Approach

  • Read current bridge.py implementation
  • Replace unsafe validation with secure pattern:

  • fp = os.path.realpath(os.path.join(BASE, body["path"].lstrip("/")))
       if not fp.startswith(os.path.realpath(BASE)):
           self.send_response(403)
           self.end_headers()
           return

  • Create security_test_bridge.py to test:
  • - ../../etc/passwd → 403
    - ../../../etc/hosts → 403
    - Symlink attacks
    - Legitimate paths → 200
  • Validate syntax with py_compile
  • Restart bridge service
  • Run security tests
  • Monitor service logs for errors
  • Work Log

    2026-04-02 03:48 PT — Slot 7

    • Created task after discovering path traversal vulnerability
    • Service crashed attempting to write to /etc via ../ bypass
    • Root cause: validation happens before path resolution
    • Creating spec and implementing fix

    2026-04-02 03:53 PT — Slot 7

    • Implemented security fix in bridge.py:
    - Changed line 32: Added os.path.realpath() to resolve paths before validation
    - Changed line 33: Added os.path.realpath(BASE)+os.sep to prevent edge case bypasses
    • Created security_test_bridge.py with 13 test cases (8 attacks + 5 legitimate paths)
    • Ran security tests: ALL 13 TESTS PASSED
    - All path traversal attempts correctly blocked (../../etc/passwd, symlinks, etc.)
    - All legitimate paths correctly allowed (site/, logs/, api.py, etc.)
    • Validated Python syntax: OK
    • Restarted scidex-bridge service: active and running
    • Verified bridge responds: /health endpoint returns 200 OK
    • Bridge service now secure against path traversal attacks
    • Result: COMPLETE — security vulnerability fixed and verified

    2026-04-20 18:45 PT — Slot 42

    • Rechecked current main state after audit reopened the task: the bridge service now lives at scripts/bridge.py, and prior commits for this task are not ancestors of origin/main.
    • Hardened upload path validation by replacing string-prefix containment with Path.resolve().relative_to(), which blocks sibling-prefix and symlink escapes after resolution.
    • Moved the no-token startup exit under __main__ so the path validator can be imported by tests without starting the bridge service.
    • Added tests/test_bridge_path_validation.py covering legitimate in-base paths, parent traversal, absolute paths, and symlink escape into a sibling directory.
    • Added a root bridge.py compatibility entrypoint because the deployed scidex-bridge.service still invokes /home/ubuntu/scidex/bridge.py while the maintained implementation lives in scripts/bridge.py.

    Already Resolved — 2026-04-24 15:20:00Z

    Evidence run on 2026-04-24:

    • git show origin/main:scripts/bridge.py confirms resolve_upload_path uses Path.resolve().relative_to() — the secure pattern.
    • git show origin/main:bridge.py confirms compatibility entrypoint delegates to scripts/bridge.py.
    • git show origin/main:tests/test_bridge_path_validation.py confirms all 5 test cases exist.
    • python3 -m pytest tests/test_bridge_path_validation.py -v5 passed (parent traversal blocked, absolute paths blocked, symlink escape blocked, legitimate paths allowed, no-shell subprocess verified).
    Commit that landed the fix: 698ed86b2 (Squash merge: orchestra/task/126b98c0-senate-db-health-check-10-abandons)

    Summary: Path traversal vulnerability fully fixed. resolve_upload_path() uses pathlib.Path.resolve().relative_to() to resolve all symlinks and relative sequences before validation, blocking all documented bypass techniques. All 5 security tests pass on main.

    Payload JSON
    {
      "requirements": {
        "coding": 9,
        "safety": 10
      },
      "completion_shas": [
        "40c9e5d5d8d839f4d986a12c2731edc6566b1fc1"
      ],
      "completion_shas_checked_at": "2026-04-21T01:39:11.364942+00:00"
    }

    Sibling Tasks in Quest (UI) ↗