Universal-AI-Hub / SECURITY.md
Alibrown's picture
Upload 58 files
5ef3828 verified
|
Raw History Blame Contribute Delete
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.