mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 19:02:16 +00:00
- Rewrite .github/workflows/desktop.yml: 2-job pipeline (validate + build-and-release) with rolling desktop-latest pre-release on push to main and stable desktop-v* releases - Add UpdateChecker component: checks for updates on startup + every 30 min, background download with progress bar, one-click relaunch - Configure Tauri updater: endpoints pointing to desktop-latest release, pubkey placeholder - Add tauri-plugin-process for relaunch support (Cargo.toml, lib.rs, package.json) - Add macOS Entitlements.plist for notarization (network + file access, no sandbox) - Add scripts/bump-desktop-version.sh for atomic version bumps across 3 config files - Add desktop/README.md with dev setup, auto-update architecture, signing docs - Update .gitignore for desktop/node_modules, dist, target - Configure macOS minimumSystemVersion, Windows timestampUrl - Include all Phase 14-21 work: agent hardening, RBAC, taint tracking, workflows, skills, knowledge graph, sessions, A2A, MCP templates, WASM sandbox, TUI dashboard, production tools, CLI expansion, API expansion, learning productionization, Tauri desktop app, and 10 new channels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
171 lines
5.3 KiB
Python
171 lines
5.3 KiB
Python
"""Tests for mount_security module."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from openjarvis.sandbox.mount_security import (
|
|
DEFAULT_BLOCKED_PATTERNS,
|
|
AllowedRoot,
|
|
MountAllowlist,
|
|
load_mount_allowlist,
|
|
validate_mount,
|
|
validate_mounts,
|
|
)
|
|
|
|
|
|
class TestDefaultBlockedPatterns:
|
|
def test_contains_ssh(self):
|
|
assert ".ssh" in DEFAULT_BLOCKED_PATTERNS
|
|
|
|
def test_contains_env(self):
|
|
assert ".env" in DEFAULT_BLOCKED_PATTERNS
|
|
|
|
def test_contains_pem(self):
|
|
assert "*.pem" in DEFAULT_BLOCKED_PATTERNS
|
|
|
|
def test_contains_key(self):
|
|
assert "*.key" in DEFAULT_BLOCKED_PATTERNS
|
|
|
|
def test_contains_gnupg(self):
|
|
assert ".gnupg" in DEFAULT_BLOCKED_PATTERNS
|
|
|
|
def test_contains_aws(self):
|
|
assert ".aws" in DEFAULT_BLOCKED_PATTERNS
|
|
|
|
def test_contains_credentials(self):
|
|
assert "credentials" in DEFAULT_BLOCKED_PATTERNS
|
|
|
|
|
|
class TestValidateMount:
|
|
def test_allows_valid_path(self, tmp_path):
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path))],
|
|
)
|
|
target = tmp_path / "data"
|
|
target.mkdir()
|
|
assert validate_mount(str(target), allowlist) is True
|
|
|
|
def test_blocks_ssh_dir(self, tmp_path):
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path))],
|
|
)
|
|
target = tmp_path / ".ssh"
|
|
target.mkdir()
|
|
assert validate_mount(str(target), allowlist) is False
|
|
|
|
def test_blocks_env_file(self, tmp_path):
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path))],
|
|
)
|
|
target = tmp_path / ".env"
|
|
target.touch()
|
|
assert validate_mount(str(target), allowlist) is False
|
|
|
|
def test_blocks_pem_file(self, tmp_path):
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path))],
|
|
)
|
|
target = tmp_path / "server.pem"
|
|
target.touch()
|
|
assert validate_mount(str(target), allowlist) is False
|
|
|
|
def test_blocks_key_file(self, tmp_path):
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path))],
|
|
)
|
|
target = tmp_path / "private.key"
|
|
target.touch()
|
|
assert validate_mount(str(target), allowlist) is False
|
|
|
|
def test_rejects_outside_root(self, tmp_path):
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path / "allowed"))],
|
|
)
|
|
target = tmp_path / "not_allowed" / "data"
|
|
target.mkdir(parents=True)
|
|
assert validate_mount(str(target), allowlist) is False
|
|
|
|
def test_no_roots_allows_non_blocked(self, tmp_path):
|
|
allowlist = MountAllowlist(roots=[])
|
|
target = tmp_path / "safe_dir"
|
|
target.mkdir()
|
|
assert validate_mount(str(target), allowlist) is True
|
|
|
|
def test_traversal_prevention(self, tmp_path):
|
|
"""Paths with .. are resolved before checking."""
|
|
allowed = tmp_path / "allowed"
|
|
allowed.mkdir()
|
|
# This resolves to tmp_path (outside allowed)
|
|
traversal = str(allowed / ".." / "secret")
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(allowed))],
|
|
blocked_patterns=["secret"],
|
|
)
|
|
assert validate_mount(traversal, allowlist) is False
|
|
|
|
|
|
class TestValidateMounts:
|
|
def test_returns_valid_mounts(self, tmp_path):
|
|
d1 = tmp_path / "data1"
|
|
d2 = tmp_path / "data2"
|
|
d1.mkdir()
|
|
d2.mkdir()
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path))],
|
|
)
|
|
result = validate_mounts([str(d1), str(d2)], allowlist)
|
|
assert len(result) == 2
|
|
|
|
def test_raises_for_blocked(self, tmp_path):
|
|
target = tmp_path / ".ssh"
|
|
target.mkdir()
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path))],
|
|
)
|
|
with pytest.raises(ValueError, match="blocked"):
|
|
validate_mounts([str(target)], allowlist)
|
|
|
|
def test_raises_for_outside_root(self, tmp_path):
|
|
target = tmp_path / "outside"
|
|
target.mkdir()
|
|
allowlist = MountAllowlist(
|
|
roots=[AllowedRoot(path=str(tmp_path / "inside"))],
|
|
)
|
|
with pytest.raises(ValueError, match="not under"):
|
|
validate_mounts([str(target)], allowlist)
|
|
|
|
def test_empty_list(self):
|
|
allowlist = MountAllowlist()
|
|
assert validate_mounts([], allowlist) == []
|
|
|
|
|
|
class TestLoadMountAllowlist:
|
|
def test_loads_from_json(self, tmp_path):
|
|
config = {
|
|
"roots": [
|
|
{"path": "/home/user/projects", "read_only": False},
|
|
{"path": "/data"},
|
|
],
|
|
"blocked_patterns": [".ssh", "*.pem"],
|
|
}
|
|
f = tmp_path / "allowlist.json"
|
|
f.write_text(json.dumps(config))
|
|
|
|
al = load_mount_allowlist(str(f))
|
|
assert len(al.roots) == 2
|
|
assert al.roots[0].path == "/home/user/projects"
|
|
assert al.roots[0].read_only is False
|
|
assert al.roots[1].read_only is True
|
|
assert al.blocked_patterns == [".ssh", "*.pem"]
|
|
|
|
def test_default_blocked_patterns(self, tmp_path):
|
|
config = {"roots": []}
|
|
f = tmp_path / "allowlist.json"
|
|
f.write_text(json.dumps(config))
|
|
|
|
al = load_mount_allowlist(str(f))
|
|
assert al.blocked_patterns == DEFAULT_BLOCKED_PATTERNS
|