Skip to content

Harden .linkedin-mcp profile/cookie permissions#283

Merged
stickerdaniel merged 6 commits intostickerdaniel:mainfrom
shuofengzhang:bug-279-secure-profile-perms
Mar 30, 2026
Merged

Harden .linkedin-mcp profile/cookie permissions#283
stickerdaniel merged 6 commits intostickerdaniel:mainfrom
shuofengzhang:bug-279-secure-profile-perms

Conversation

@shuofengzhang
Copy link
Copy Markdown
Contributor

What changed

  • Added permission hardening helpers in linkedin_mcp_server/core/browser.py:
    • _secure_profile_dirs(...) ensures profile directories are owner-only (0700).
    • _set_private_mode(...) applies explicit POSIX modes for sensitive paths.
  • Updated BrowserManager.start() to use _secure_profile_dirs(...) instead of plain mkdir(...).
  • Updated BrowserManager.export_cookies(...) to:
    • ensure cookie parent directories under .linkedin-mcp are private,
    • write cookie files with owner-only permissions (0600).
  • Added tests/test_browser_security.py with coverage for:
    • hardening .linkedin-mcp/profile directory permissions,
    • keeping unrelated non-LinkedIn parent dirs untouched,
    • enforcing owner-only cookie file permissions.

Insight / Why this matters

  • Root cause: profile/cookie paths were created/written with default filesystem permissions (mkdir/write_text), which inherit umask. On common setups (umask 022), this yields world-readable paths (0755 directories, 0644 files).
  • Why it is easy to miss: functionality works normally, and most tests/CI focus on behavior rather than permission bits, so insecure defaults can persist unnoticed.
  • Practical impact: LinkedIn session artifacts (profile state, auth cookies) can be exposed to other local users on shared machines.

Practical gain / Why this matters

  • Immediate security hardening of sensitive local persistence paths with minimal behavioral risk.
  • Reduces accidental credential/session leakage in multi-user environments.
  • Adds regression tests so permission hardening does not silently regress.

Why

  • This is a low-risk, high-signal fix: it tightens local data confidentiality without changing scraping/auth flow or API behavior.
  • Tradeoff: explicit POSIX mode enforcement only applies to sensitive paths and is skipped on Windows, preserving cross-platform behavior.

Testing

  • pytest tests/test_browser_security.py -q
  • scripts/clone_and_test.sh stickerdaniel/linkedin-mcp-server (157 passed)

Fixes #279

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 28, 2026

Greptile Summary

This PR hardens the file-system permissions of LinkedIn session artifacts (browser profile directory, cookie files, storage-state snapshots) that were previously written with default umask-inherited permissions, making them world-readable on common setups.

Key changes:

  • _harden_linkedin_tree(path) — new helper that walks up from path to the .linkedin-mcp ancestor and chmods each directory to 0o700. It is idempotent (skips directories already at 0o700), Windows-safe, and bounded: it stops exactly at .linkedin-mcp and never touches parent directories outside the LinkedIn tree.
  • export_cookies now calls secure_mkdir + _harden_linkedin_tree on the cookie parent directory, then uses the pre-existing secure_write_text for the actual write. secure_write_text is already TOCTOU-free: it creates a temp file via mkstemp (inherently 0o600), chmods it, and atomically os.replaces it into place — so the previous thread concern about cookie-file exposure is fully resolved.
  • export_storage_state calls _harden_linkedin_tree on the parent directory (hardening it to 0o700 before any write) and then chmods the output file to 0o600 after Playwright writes it. The post-write chmod is an unavoidable Playwright API limitation, but since the parent is already 0o700 before the write begins, no other OS user can observe the file during the transient window.
  • BrowserManager.start() now calls _harden_linkedin_tree after secure_mkdir to harden any pre-existing profile directories that were created before this fix landed.
  • tests/test_browser_security.py — new test file with five focused tests: directory hardening, boundary cases (unrelated parents, paths outside .linkedin-mcp), and integration-level permission assertions for both export code paths.

Confidence Score: 5/5

Safe to merge — all prior review concerns are addressed and no new defects are introduced.

The previous TOCTOU concern on export_cookies is fully resolved by routing through secure_write_text (atomic temp-file swap). The remaining post-write chmod in export_storage_state is an inherent Playwright API constraint, but is practically mitigated because the parent directory is hardened to 0o700 before Playwright writes, blocking other OS users from even listing the directory during the window. _harden_linkedin_tree is correctly bounded, idempotent, and Windows-safe. Test coverage is thorough. No P0 or P1 issues found.

No files require special attention.

Important Files Changed

Filename Overview
linkedin_mcp_server/core/browser.py Adds _harden_linkedin_tree helper and integrates it with secure_mkdir across start(), export_cookies(), and export_storage_state(); cookie writes are now fully atomic via the existing secure_write_text; storage-state TOCTOU remains but is practically mitigated by the already-hardened parent directory.
tests/test_browser_security.py New test file covering directory hardening, boundary conditions (unrelated parents, outside-.linkedin-mcp paths), and end-to-end permission assertions for both cookie and storage-state export paths; all POSIX tests correctly skipped on Windows.

Reviews (4): Last reviewed commit: "Merge branch 'main' into bug-279-secure-..." | Re-trigger Greptile

Comment thread linkedin_mcp_server/core/browser.py Outdated
Comment thread linkedin_mcp_server/core/browser.py Outdated
@stickerdaniel
Copy link
Copy Markdown
Owner

Thanks for the contribution! #279 was resolved by me in #280 and #282 while this was in flight, but the core/browser py hardening (profile dirs + cookie export) secure_mkdir is still a gap I didn't cover. Could you rebase, and use secure_mkdir from common_utils instead of the custom helpers?

@shuofengzhang shuofengzhang force-pushed the bug-279-secure-profile-perms branch from a81b4d1 to f7170d6 Compare March 28, 2026 23:35
shuofengzhang and others added 4 commits March 28, 2026 23:38
- Replace custom _secure_profile_dirs/_set_private_mode with thin
  _harden_linkedin_tree that uses secure_mkdir from common_utils
- Fix export_storage_state: chmod 0o600 after Playwright writes
- Add test for export_storage_state permission hardening
- Add test for no-op outside .linkedin-mcp tree
- Revert unrelated loaders.py change
@stickerdaniel stickerdaniel merged commit f046dfb into stickerdaniel:main Mar 30, 2026
5 checks passed
stickerdaniel added a commit that referenced this pull request Apr 2, 2026
Harden .linkedin-mcp profile/cookie permissions
stickerdaniel added a commit that referenced this pull request Apr 2, 2026
Harden .linkedin-mcp profile/cookie permissions
stickerdaniel added a commit that referenced this pull request Apr 3, 2026
Harden .linkedin-mcp profile/cookie permissions
stickerdaniel added a commit that referenced this pull request Apr 17, 2026
Harden .linkedin-mcp profile/cookie permissions
Naman-B-Parlecha pushed a commit to Naman-B-Parlecha/linkedin-mcp-server that referenced this pull request Apr 17, 2026
…e-profile-perms

Harden .linkedin-mcp profile/cookie permissions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Directories under ~/.linkedin-mcp created world-readable (missing secure_mkdir)

2 participants