diff --git a/.claude/skills/convert-to-company-docs/convert.py b/.claude/skills/convert-to-company-docs/convert.py index fde150d06..386dbf0ca 100644 --- a/.claude/skills/convert-to-company-docs/convert.py +++ b/.claude/skills/convert-to-company-docs/convert.py @@ -26,6 +26,32 @@ sys.exit(1) +# M-05: Allowed directories for file access (path restriction) +ALLOWED_UPLOAD_DIRS = [ + 'knowledge/', + 'docs/', + 'agents/', + 'core/', + 'logs/', +] + +PROJECT_ROOT = Path(os.environ.get('CLAUDE_PROJECT_DIR', '.')).resolve() + + +def _is_path_allowed(file_path: str) -> bool: + """ + M-05: Validate that file path is within allowed directories. + Prevents converting arbitrary files (e.g., .env, credentials) to Google Docs. + """ + try: + resolved = Path(file_path).resolve() + rel = resolved.relative_to(PROJECT_ROOT) + rel_str = str(rel).replace('\\', '/') + return any(rel_str.startswith(prefix) for prefix in ALLOWED_UPLOAD_DIRS) + except (ValueError, RuntimeError): + return False + + # Cores Company (RGB normalizado 0-1) COLORS = { 'background': {'red': 0.953, 'green': 0.953, 'blue': 0.953}, # #F3F3F3 @@ -314,6 +340,12 @@ def convert(self, md_path: str, folder_id: str = None) -> dict: print(f"ERRO: Arquivo nao encontrado: {md_path}") return None + # M-05: Validate file path is within allowed directories + if not _is_path_allowed(md_path): + print(f"ERRO [SECURITY]: Conversao bloqueada — path fora dos diretorios permitidos: {md_path}") + print(f" Diretorios permitidos: {ALLOWED_UPLOAD_DIRS}") + return None + # Autenticar if not self.docs_service: if not self.authenticate(): diff --git a/.claude/skills/sync-docs/gdrive_sync.py b/.claude/skills/sync-docs/gdrive_sync.py index 1f0517ddd..46f170d1a 100644 --- a/.claude/skills/sync-docs/gdrive_sync.py +++ b/.claude/skills/sync-docs/gdrive_sync.py @@ -23,6 +23,35 @@ sys.exit(1) +# M-05: Allowed directories for upload (path restriction) +ALLOWED_UPLOAD_DIRS = [ + 'knowledge/', + 'docs/', + 'agents/', + 'core/', + 'logs/', +] + +PROJECT_ROOT = Path(os.environ.get('CLAUDE_PROJECT_DIR', '.')).resolve() + + +def _is_upload_path_allowed(local_path: str) -> bool: + """ + M-05: Validate that upload path is within allowed directories. + Prevents uploading arbitrary files (e.g., .env, credentials) to Google Drive. + """ + try: + resolved = Path(local_path).resolve() + # Must be under project root + rel = resolved.relative_to(PROJECT_ROOT) + rel_str = str(rel).replace('\\', '/') + # Must start with one of the allowed prefixes + return any(rel_str.startswith(prefix) for prefix in ALLOWED_UPLOAD_DIRS) + except (ValueError, RuntimeError): + # relative_to raises ValueError if not relative + return False + + class GDriveSync: """Sincronizador de arquivos com Google Drive usando OAuth.""" @@ -204,6 +233,12 @@ def upload_file(self, local_path: str, folder_id: str = None, folder_name: str = print(f"ERRO: Arquivo nao encontrado: {local_path}") return None + # M-05: Validate upload path is within allowed directories + if not _is_upload_path_allowed(local_path): + print(f"ERRO [SECURITY]: Upload bloqueado — path fora dos diretorios permitidos: {local_path}") + print(f" Diretorios permitidos: {ALLOWED_UPLOAD_DIRS}") + return None + # Determina pasta destino target_folder_id = folder_id if not target_folder_id: diff --git a/.claude/skills/sync-docs/reauth.py b/.claude/skills/sync-docs/reauth.py index 2e5c0fd54..35af5c8e0 100644 --- a/.claude/skills/sync-docs/reauth.py +++ b/.claude/skills/sync-docs/reauth.py @@ -21,9 +21,9 @@ 'https://www.googleapis.com/auth/drive.file' ] -# Paths -OAUTH_KEYS = Path(r"~/.config/mcp-gdrive/gcp-oauth.keys.json") -TOKEN_FILE = Path(r"~/.config/mcp-gdrive/.gdrive-server-credentials.json") +# Paths — use Path.home() for cross-platform ~ expansion (M-06 fix) +OAUTH_KEYS = Path.home() / ".config" / "mcp-gdrive" / "gcp-oauth.keys.json" +TOKEN_FILE = Path.home() / ".config" / "mcp-gdrive" / ".gdrive-server-credentials.json" def main():