The repo ships a pre-commit hook that blocks commits containing secrets,
.env files, and sensitive credential formats. Wire it up once per clone:
git config core.hooksPath .githooks
chmod +x .githooks/pre-commitAfter that, every git commit runs through the hook automatically. To allow a
detected false positive, append # pragma: allowlist secret on the same line:
# This is a fake key used in tests, not a real one:
test_key = "sk-test123456789012345678901234567890" # pragma: allowlist secret| Category | Pattern / Rule |
|---|---|
.env files |
Any .env, .env.local, .env.production, etc. — except .env.example |
| Key / cert files | *.pem, *.key, *.p12, *.pfx, *.jks, *.keystore |
| OpenAI API key | sk-[A-Za-z0-9_-]{20,} |
| Anthropic API key | sk-ant-[A-Za-z0-9_-]{20,} |
| Google OAuth secret | GOCSPX-[A-Za-z0-9_-]{20,} |
| Google API key | AIza[0-9A-Za-z_-]{35} |
| AWS access key | AKIA[0-9A-Z]{16} |
| Discord bot token | M…{23}.…{6}.…{27} triple-segment |
| PEM private key | -----BEGIN … PRIVATE KEY----- |
| Slack token | xox[abprs]-… |
| GitHub PAT | ghp_…{36} |
| Stripe live secret | sk_live_…{24} |
PlumeAI is a single-tenant, self-hosted assistant. It is not designed for multi-tenant SaaS hosting. Its threat model focuses on:
- Operator credentials at rest (provider keys, OAuth tokens, bot tokens) — encrypted AES-GCM with
ENCRYPTION_KEY. - Session cookies — JWT HS256,
HttpOnly,SameSite=Lax,Securein non-dev. - SSRF —
app/tools/base.py:assert_public_urlblocks private/loopback/link-local before any user-controlled fetch. - OAuth CSRF — random
statecookie verified on callback (Google integrations).
| # | Severity | Area | Finding | Status |
|---|---|---|---|---|
| 1 | High | Auth | Admin password hashed with raw SHA-256 (no salt, no work factor). GPU brute-force is feasible on short passwords. | Open — recommend migration to argon2/bcrypt. Mitigated in practice by AUTH_SECRET rotation + admin-only access. |
| 2 | Low | Docker compose | docker-compose.yml provides dev-safe fallbacks for AUTH_SECRET, ENCRYPTION_KEY, ADMIN_PASSWORD_HASH. |
Acceptable — documented in .env.example. Production deployments must override via .env. |
| 3 | Info | Error handling | Unhandled errors log full stack traces server-side but expose only a generic message + request_id to the client. |
OK |
| 4 | Info | Cookies | Session cookie uses HttpOnly=True, SameSite=Lax, Secure when APP_ENV != "dev". |
OK |
| 5 | Info | SSRF | All user-controlled URLs (web_fetch, http) pass through assert_public_url. |
OK |
| 6 | Info | XSS | Markdown rendered with react-markdown defaults — raw HTML disabled, no dangerouslySetInnerHTML. |
OK |
| 7 | Info | CORS | No CORSMiddleware configured — frontend served behind the same nginx, same-origin requests only. |
OK for the current architecture. Add strict CORS if exposing the API cross-origin. |
| 8 | Info | OAuth | Unified Google callback /api/tools/google/oauth/callback, random state cookie verified, redirect URI anchored on FRONTEND_URL. |
OK |
| 9 | Info | Crypto | AES-GCM 256-bit, 12-byte random IV, format binary-compatible with Node Web Crypto. | OK |
| 10 | Info | Secrets in repo | No real secrets found in tracked files or git history. Only placeholders (GOCSPX-..., apps.googleusercontent.com as documentation). |
OK |
- Migrate admin password hashing from SHA-256 to argon2id via
passlib[argon2]. Single function swap inapp/auth.py:check_admin_password. Existing hashes become invalid → user re-sets password on first login.
If you find a security issue, please report it