How your data is protected and security best practices.
The PigeonAI is designed with privacy and security as core principles:
┌─────────────────────────────────────────────────────────────────────┐
│ SECURITY ARCHITECTURE │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ CREDENTIALS │ │ MESSAGES │ │ AI INFERENCE │ │
│ │ │ │ │ │ │ │
│ │ OS Keyring │ │ AES-256 │ │ 100% Local │ │
│ │ (encrypted) │ │ Encrypted │ │ (no cloud) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ Nothing leaves your machine without your knowledge │
└─────────────────────────────────────────────────────────────────────┘
What's protected: API ID, API hash, session string, encryption key
How it's protected:
- Stored in OS keyring (not plain text files)
- macOS: Keychain Access (AES-256, Secure Enclave)
- Windows: Credential Manager (DPAPI encryption)
- Linux: GNOME Keyring / Secret Service (AES-128)
Code location: src/storage/credentials.py
# Credentials never touch the filesystem
creds = CredentialManager()
creds.store_api_hash(api_hash) # Goes straight to keyringWhat's encrypted: Message text, reply drafts, style profiles
How it's encrypted:
- Algorithm: AES-256 via Fernet
- Key derivation: PBKDF2 with SHA256
- Iterations: 480,000 (OWASP recommended)
- Salt: Unique random salt per installation
Code location: src/storage/encryption.py
class EncryptionManager:
def __init__(self, password: str, salt_path: Path):
# Derive key from password + salt
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=self._load_or_create_salt(salt_path),
iterations=480_000, # OWASP recommendation
)
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
self._fernet = Fernet(key)
def encrypt(self, plaintext: str) -> str:
return self._fernet.encrypt(plaintext.encode()).decode()
def decrypt(self, ciphertext: str) -> str:
return self._fernet.decrypt(ciphertext.encode()).decode()What's stored: Contacts, messages (encrypted), replies, style profiles
Security measures:
- SQLite database stored locally
- Message content encrypted before storage
- Database file permissions: owner-only (0600)
- No foreign connections possible (file-based)
Schema security:
-- Messages are stored encrypted
INSERT INTO messages (encrypted_text, ...)
VALUES ('gAAAAABk...encrypted_content...', ...);
-- Plain text never storedTelegram session:
- Session string stored in keyring (not file)
- Enables reconnection without re-auth
- If compromised, can be revoked from Telegram settings
Revoking a session:
- Go to Telegram Settings > Devices
- Find "Unknown session" or suspicious entry
- Click "Terminate session"
No cloud API means:
- Messages never sent to OpenAI, Anthropic, or Google
- No API logs of your conversations
- No third-party data retention
- Complete control over your data
What Ollama sees:
- Only what you explicitly send to it
- Running on your machine
- No telemetry or logging by default
| Attack Vector | Difficulty | Mitigation |
|---|---|---|
| Physical access to machine | High | Full disk encryption |
Stolen .env file |
Medium | Don't commit to git, use keyring |
| Keyring access | High | Requires OS password |
| Database file theft | Medium | Messages are encrypted |
| Network interception | Low | MTProto is encrypted |
| Ollama API access | Low | Bind to localhost only |
- Enable full disk encryption (FileVault, BitLocker, LUKS)
- Use a strong OS password
- Never commit
.envto git - Keep encryption key in keyring, not
.env - Regularly check Telegram active sessions
| Data Type | Storage | Encryption |
|---|---|---|
| API credentials | Keyring | OS-level |
| Session string | Keyring | OS-level |
| Message content | Database | AES-256 |
| Contact names | Database | Optional |
| Style profiles | Database | AES-256 |
| Phone number | Not stored | N/A |
| 2FA password | Not stored | N/A |
- Your Telegram password
- 2FA passwords
- Phone verification codes
- Plain text message content (when encryption enabled)
The app uses Telegram's MTProto protocol which provides:
- End-to-end encryption for secret chats
- Transport layer encryption for all communications
- Perfect forward secrecy
- Server authentication
Your api_id and api_hash identify your application to Telegram:
- api_id: Public identifier (like a username)
- api_hash: Secret key (like a password)
Best practices:
- Never share your
api_hash - Don't commit credentials to public repos
- Regenerate if compromised
Aggressive automation can get your account banned:
# Built-in protection
rate_limiter = RateLimiter(
messages_per_minute=20, # Conservative limit
burst_limit=5, # Prevent rapid fire
)# Set encryption key in keyring
python -c "
from src.storage.credentials import CredentialManager
import secrets
creds = CredentialManager()
creds.store_encryption_key(secrets.token_urlsafe(32))
print('Encryption key generated and stored')
"If you need to disable encryption:
# In main.py, skip encryption initialization
db = Database(
db_path=settings.database_path,
encryption_manager=None, # No encryption
)# Set secure permissions on data directory
chmod 700 data/
chmod 600 data/assistant.db
chmod 600 data/.salt
chmod 600 .env- Your Telegram messages (to learn style)
- Your contacts (to track importance)
- Your writing patterns (style profile)
- Message content (local LLM)
- Style analysis (local processing)
- Contact information (local database)
- Authentication requests
- Message fetch requests
- Messages you send (through the app)
- No analytics
- No telemetry
- No crash reports
- No usage tracking
-
Revoke Telegram session:
- Telegram Settings > Devices > Terminate all other sessions
-
Regenerate API credentials:
- Go to my.telegram.org/apps
- Delete the application
- Create a new one
-
Clear local credentials:
from src.storage.credentials import CredentialManager CredentialManager().clear_all()
-
Change encryption key:
rm data/.salt # Remove old salt # Re-run setup to generate new key
Without the encryption key, the attacker gets:
- Encrypted blobs (useless without key)
- Contact Telegram IDs (semi-public info)
- Timestamps (metadata only)
With the encryption key, they get:
- Message content
- Your style profile
- Reply history
Mitigation: Keep encryption key in keyring, not .env file.
Before deploying:
- Encryption enabled (key in keyring)
-
.envexcluded from git -
data/directory excluded from git - File permissions set (700/600)
- Ollama bound to localhost only
- Full disk encryption enabled
- Strong OS password set
-
Physical access with OS password - If someone has your laptop password, they can access everything
-
Memory forensics - Decrypted messages exist in RAM while processing
-
Compromised Ollama - A malicious Ollama build could log prompts
-
Telegram server-side - Telegram can read non-secret chats
- Use Telegram secret chats for highly sensitive conversations
- Enable auto-lock on your computer
- Use official Ollama releases only
- Regularly review Telegram active sessions
| Aspect | This App (Local) | Cloud API |
|---|---|---|
| Message visibility | Only you | You + provider |
| Data retention | Your control | Provider policy |
| Encryption | AES-256 + keyring | Provider's choice |
| Audit trail | Your logs only | Provider logs |
| Jurisdiction | Your location | Provider's servers |
| Subpoena risk | Your device only | Provider + you |
If you discover a security vulnerability:
- Do not open a public GitHub issue
- Email the maintainer directly
- Provide detailed reproduction steps
- Allow reasonable time for fix before disclosure