Spaces:
Running
Running
|
Download SECURITY.md from codey-lab/Universal-AI-Hub: direct link, hf CLI and curl.
- Browser
- Download file 6.68 kB
-
https://huggingface.co/spaces/codey-lab/Universal-AI-Hub/resolve/main/SECURITY.md
- Command line
-
hf download hf://spaces/codey-lab/Universal-AI-Hub/SECURITY.md
-
curl -L -o SECURITY.md https://huggingface.co/spaces/codey-lab/Universal-AI-Hub/resolve/main/SECURITY.md
6.68 kB
| # Security Policy | |
| ## Reporting a Vulnerability | |
| If you find a security issue, please do **not** open a public GitHub issue. | |
| Report privately via: [GitHub Security Advisories](https://github.com/VolkanSah/Universal-MCP-Hub/security/advisories/new) | |
| Include: what you found, how to reproduce it, and what impact you think it has. You'll get a response within 72 hours. If the issue is confirmed, a fix will be prioritized before any public disclosure. | |
| --- | |
| ## Security Architecture | |
| ### The Guardian Pattern | |
| `main.py` is the only process that ever touches secrets. It reads `.env` / HF Space Secrets, initializes `fundaments/*` conditionally, and passes the results into `app/*` as a validated dict. After that handoff, `app/*` has no path back to the raw environment. | |
| ``` | |
| .env / HF Secrets | |
| β | |
| βΌ | |
| main.py (Guardian) β only entry point | |
| β | |
| β initializes fundaments/* conditionally | |
| β injects as: fundaments = {"config": ..., "db": ..., "encryption": ..., ...} | |
| β | |
| βΌ | |
| app/app.py β unpacks fundaments ONCE at startup, never stores globally | |
| β | |
| βββ app/* modules β read only from app/.pyfun, never from os.environ | |
| ``` | |
| This is enforced structurally. `app/*` has no import of `fundaments/`, no `os.environ` calls, no `.env` access. It's not a coding convention β it's an import boundary. | |
| --- | |
| ### Secret Handling | |
| - API keys live exclusively in HF Space Secrets or `.env` β never in `app/.pyfun`, never in source code | |
| - `app/.pyfun` references only ENV variable **names** (e.g. `env_key = "ANTHROPIC_API_KEY"`) β never values | |
| - `list_active_tools` returns key names only β the actual key values are never exposed through any MCP tool | |
| - `fundaments/` modules are initialized only if their required ENV vars are present β missing config = service skipped, not crashed | |
| --- | |
| ### Encryption (`fundaments/encryption.py`) | |
| When `MASTER_ENCRYPTION_KEY` and `PERSISTENT_ENCRYPTION_SALT` are set: | |
| - Key derivation: **PBKDF2-HMAC-SHA256**, 480,000 iterations, 256-bit output | |
| - Encryption: **AES-256-GCM** β authenticated encryption, provides both confidentiality and integrity | |
| - Each encryption operation uses a fresh random 96-bit nonce | |
| - Authentication tag (128-bit) is stored alongside ciphertext β decryption fails loudly if data was tampered | |
| - File encryption uses streaming with 8 KB chunks β nonce prepended, tag appended to file | |
| - If encryption keys are not set, the service is skipped cleanly β the app runs without it | |
| --- | |
| ### Authentication (`fundaments/user_handler.py`) | |
| - Passwords hashed with **PBKDF2-SHA256** via `passlib` | |
| - Sessions stored in SQLite with IP address + User-Agent binding β `validate_session()` checks both | |
| - Session IDs are regenerated on login (session fixation prevention) | |
| - Failed login attempts tracked per user β account locks after 5 consecutive failures | |
| - Session table uses `ON DELETE CASCADE` β deleting a user removes all their sessions automatically | |
| --- | |
| ### Access Control (`fundaments/access_control.py`) | |
| - Role-based permission system backed by PostgreSQL (cloud DB, Guardian-only) | |
| - Schema: `users β user_role_assignments β user_roles β role_permissions β user_permissions` | |
| - All queries use parameterized statements via `asyncpg` β no string interpolation | |
| - `has_permission()` checks the full roleβpermission chain per request | |
| - `AccessControl` only initializes if a DB connection is available β degrades gracefully otherwise | |
| --- | |
| ### Database Security (`fundaments/postgresql.py`) | |
| **Cloud DB (asyncpg pool):** | |
| - SSL enforced at minimum `sslmode=require` β upgraded automatically if not set | |
| - Neon.tech quirks handled: `statement_timeout` stripped from DSN (unsupported, causes silent failures), keepalives set | |
| - Credentials masked in all log output β full DSN only in `DEBUG` level logs | |
| - SSL runtime check via `pg_stat_ssl` on every new connection (cloud providers: warning only if unavailable) | |
| - Automatic pool restart on Neon connection termination (sqlstate `08006`) | |
| - Pool: min 1 / max 10 connections, connect timeout 5s, command timeout 30s | |
| **Internal SQLite (`app/db_sync.py` + `fundaments/user_handler.py`):** | |
| - Same SQLite file, separate tables β zero overlap enforced by code | |
| - `users` + `sessions` β owned by `user_handler.py` (Guardian layer) | |
| - `hub_state` + `tool_cache` β owned by `db_sync.py` (app/* layer) | |
| - `db_query` MCP tool exposes SELECT-only access to `hub_state` and `tool_cache` β cannot reach `users` or `sessions` | |
| - SELECT enforcement: non-SELECT queries raise `ValueError` before execution | |
| - On HuggingFace Spaces: SQLite auto-relocated to `/tmp/` (HF filesystem is read-only outside `/tmp/`) | |
| --- | |
| ### MCP Transport Security | |
| - All MCP traffic routes through Quart `/mcp` endpoint before reaching FastMCP | |
| - This is the natural interception point for auth checks, rate limiting, and payload logging β none are optional add-ons | |
| - `/crypto` endpoint exists but returns `501` until the app-layer encryption wrapper is implemented β not bypassed, not silently ignored | |
| - `health_check` and `list_active_tools` are always available β they expose no secrets, only status and key names | |
| --- | |
| ### Sandboxing Rules | |
| These rules are enforced by code structure, not just documentation: | |
| | Rule | Enforced by | | |
| | :--- | :--- | | |
| | `app/*` never reads `os.environ` | No `os.environ` calls in any `app/` file | | |
| | `app/*` never imports `fundaments/` | No cross-imports between layers | | |
| | `fundaments/` services injected, not constructed in `app/*` | `start_application(fundaments)` signature | | |
| | Direct execution of `app/app.py` blocked | `__main__` guard with null-fundaments warning | | |
| | `db_query` tool is SELECT-only | `ValueError` raised before any non-SELECT execution | | |
| | API keys never in `.pyfun` | `.pyfun` parser only reads key names, never resolves them | | |
| --- | |
| ### What This Project Does Not Claim | |
| - It is not a penetration-tested security product | |
| - The encrypted `/crypto` endpoint is not yet implemented | |
| - Search provider implementations (Brave, Tavily) are pending β the tool registers as placeholder | |
| - This is a personal/community project β use in production at your own assessment | |
| > PyFundaments is not perfect. But it's more secure than most of what runs in production today. | |
| --- | |
| ## Supported Versions | |
| | Version | Supported | | |
| | :--- | :--- | | |
| | latest (`main`) | β | | |
| | older forks | β β fork at your own risk | | |
| --- | |
| ## License Note | |
| This project is dual-licensed under Apache 2.0 and the [Ethical Security Operations License v1.1 (ESOL)](ESOL). Using this software for surveillance, unauthorized access, or harm to others automatically terminates your license. | |