PROJECT JAMES is in alpha — v0.3.0 (Platform Skeleton) — security-focused by design, but not production-ready.
This document describes:
- The security model and threat assumptions
- What JAMES protects against today
- Known limitations
- How to report vulnerabilities
For the formal assurance case (per-requirement arguments with code
citations, OpenSSF assurance_case criterion), see
docs/security/ASSURANCE_CASE.md.
JAMES implements defense-in-depth across the RAG pipeline.
[Input] → Instruction Isolation Filter (31+ injection patterns)
↓
[Search] → Graph-level RBAC + ABAC sensitivity gating
↓
[Output] → PII masking + role-based content filter
4 roles with hierarchical permissions:
| Role | Sensitivity Access | Operations |
|---|---|---|
| admin | public→secret | All (incl. settings) |
| manager | public→confidential | Most (no admin) |
| employee | public→internal | Read + standard ops |
| external | public only | Read public only |
4 sensitivity levels for every entity:
public— all rolesinternal— employee+confidential— manager+secret— admin only
- JWT tokens with HS256 signing
- 8-hour expiration (configurable via
JWT_EXPIREincore/auth.py) - Passwords stored as bcrypt (W4 P1-A, 2026-05-11) with an
algorithm envelope (
bcrypt$...). Pre-W4 unsalted SHA256 hex digests are accepted on input only and rewritten to bcrypt on the next successful login (transparent migration). - Rate limiting: 30 requests / 60 seconds per IP across
/query/,/upload/,/login/,/signup/,/password/reset/confirm - Full audit log in SQLite (every request, decision, denial)
POST /signup/is public and creates a row withactive=0,role=external. The account cannot log in until an admin approves and assigns a role.- Success and duplicate produce the same 200 response body — an
anonymous caller cannot enumerate existing usernames through this
endpoint. The audit log distinguishes (
signup_pendingvssignup_rejected_duplicate) for operator review. - Password policy: 8..72 characters (72 = bcrypt input window), must contain at least one letter and one digit. Korean and other unicode letters count. The 72-char ceiling is a hard reject rather than silent truncation so two long passwords cannot ever collide.
- Username policy: 3..32 characters, lowercase + digits +
_-only. Case-folding collisions (Adminvsadmin) are blocked at signup, not at login.
POST /api-keys/issuemints a long-livedjms_<token>for the JWT-authenticated user. Plaintext is returned exactly once; onlySHA256(token)is persisted.GET /api-keys/listreturns the caller's keys by prefix, label, timestamps, and revocation flag. Plaintext is never returned.POST /api-keys/revokerevokes a key by its 12-character prefix. Scope is enforced by the owning user — a caller cannot revoke another user's key.- Revocation is one-way (no unrevoke). Rotation = revoke + issue.
- Request authentication (W4 P3-2): the server accepts a user key
via
X-API-Keyheader or?api_key=query. Resolved role comes from the owning user — an admin user's key passes admin gates; an employee's does not. JWT still wins when both are present. The legacy systemJAMES_API_KEYdoes NOT self-elevate to admin — admin endpoints require pairing with an admin JWT.
POST /password/changelets a logged-in user rotate their own password. Identity comes from the JWT subject claim — the request body never names a user.POST /admin/users/issue-reset-tokenlets an admin mint a one-shot reset token for a target user. The plaintext token is returned once; only SHA256(token) is persisted. 1-hour TTL. Issuing a new token revokes any prior unused token for the same user.POST /password/reset/confirmaccepts{username, token, new_password}. Replay is blocked by aused_atflag written in the same transaction as the password UPDATE. Token-validity errors (unknown / expired / wrong username / already used) all collapse to a single 401 so anonymous callers cannot enumerate.
- Prompt injection: 31+ pattern detection + instruction isolation
- Privilege escalation: 3-stage check at vector / graph / output
- Data exfiltration: PII masking, role-based output filter
- Brute force: Rate limiting + audit logging
- Hardcoded secrets: All keys via environment variables
- Replay attacks: JWT expiration + signature verification
- Risky-coding requests (#8 v0.2): hard-refuse policy for queries asking the model to produce destructive shell / SQL / git / file commands — see §2.4 below
- Tool abuse: Sandboxed Python execution, but advanced sandbox escape attacks may succeed
- Memory poisoning: Source-tagged memory with role-based writes, but adversarial entries can pollute long-term store
- Web search injection: Tavily/DDG content is treated as untrusted, but malicious content could still affect downstream LLM responses
JAMES applies a hard-refuse policy to queries that ask the assistant to produce a clearly destructive command. The trigger fires before the LLM is called — no command is ever generated, no warnings-then-answer pattern is shown.
What is blocked
| Family | Examples |
|---|---|
| Filesystem-wide deletion | rm -rf /, del /f /s /q, dd if=, shred, mkfs.*, format c: |
| SQL drop / truncate | drop database, drop table, truncate table |
| Destructive git | git reset --hard, git push --force, git clean -fdx |
| Process kill | kill -9, killall |
| Scope-wide deletion (English) | delete all files in /home, wipe every database |
| Scope-wide deletion (Korean) | wiki 폴더의 모든 파일을 삭제하는 명령어, 데이터베이스 초기화 명령 |
The matched query receives the same 자료에 없음. 보안 정책에 의해 차단되었습니다. response as a prompt-injection block — the two are byte-identical so an audit consumer cannot distinguish them externally. Patterns live in core/security_layer.py::RISKY_CODING_REGEX.
What is NOT blocked
The matcher is intentionally narrow. A documentation question about how a destructive command works is generally fine — only its use against a target is blocked:
- "rm 명령의 옵션 종류" → allowed (no
-rftoken + no scope marker) - "git push가 무엇인가요?" → allowed
- "이메일 삭제하는 단축키" → allowed (no
전체/모든scope marker before 삭제)
If a legitimate request is blocked, the operator can:
- Rephrase without the destructive verb + scope-marker pair (most common fix)
- Disable the gate temporarily by removing the call site in
core/security_layer.py::pre_check(admin-only operation, requires git push by the maintainer — this is intentional friction)
Why hard-refuse instead of warn-and-answer
The earlier behavior (mode=coding answering with a 🚨 prefix) shipped commands paired with warnings. This violates the principle that the assistant should not produce output an operator wouldn't paste from a vendor manual. The block message names the policy; the user can ask again with a non-destructive framing.
- Network-layer attacks (run JAMES behind reverse proxy / firewall)
- Physical access to the host machine
- Compromised LLM weights or supply chain (Ollama / dependencies)
- Side-channel attacks (timing, cache)
- Denial of service beyond basic rate limiting
JAMES has been tested with:
- 65-item internal diagnostic (8 sections)
- 83-item adversarial security test (100% pass on synthetic adversarial inputs)
JAMES has not been tested against:
- Real adversarial users at scale
- Coordinated multi-vector attacks
- Production-grade red team
- Specific compliance frameworks (SOC2, HIPAA, GDPR)
Synthetic-data testing does not equal production security. Before any sensitive deployment:
- Independent security review
- Penetration testing
- Compliance audit (if applicable)
- Network isolation review
- Backup and incident response plan
- JWT secret: Defaults to a placeholder. Must be set via
JAMES_JWT_SECRETenv var with 32+ random characters before any non-development use. - API key: Default value insufficient. Use a strong random key for
JAMES_API_KEY. - HTTPS: Server runs on plain HTTP by default. Production deployment requires reverse proxy with TLS (nginx/caddy).
- Multi-tenancy: Not implemented. Single-tenant only in v0.1.
- LLM hallucination: Even with Graph-RAG, responses may contain inaccuracies. Always verify critical information.
If you discover a security vulnerability, please do not open a public GitHub issue. Use one of the private channels below.
- Open https://github.com/Hashevolution/James-RAG-Evol/security/advisories/new
- Fill in the form: title, description, affected versions, severity, optional patch
- Submit — only repository maintainers will see the report
This channel is preferred because it integrates with GitHub Security Advisories and (when applicable) CVE assignment.
If you cannot use GitHub PVR, email karu-7@hanmail.net with:
- Steps to reproduce
- Affected component / version
- Suggested severity (per CVSS if possible)
- Suggested mitigation (if any)
We aim to:
- Acknowledge within 7 days
- Provide initial assessment within 14 days
- Release a fix or workaround within 30 days for high-severity issues
You will receive credit in the security advisory unless you prefer otherwise.
# Strong API key (32+ chars recommended)
JAMES_API_KEY=<random-32-char-string>
# JWT signing secret (NEVER commit, regenerate per environment)
JAMES_JWT_SECRET=<random-32-char-string>
# Generate with:
# python -c "import secrets; print(secrets.token_urlsafe(32))"- Run behind reverse proxy (nginx, Caddy)
- Enable TLS / HTTPS
- Restrict origin via CORS
- Set
JAMES_PROTECTED_FILESto prevent tool access to critical files - Enable audit log retention (default: SQLite, no auto-rotation)
- Regular backup of
wiki/,memory/, audit DB - Monitor
[SEC]log entries for anomalies
- v0.1.0-alpha (2026): Initial security model documented.
- v0.2.0 (2026-05-08 → 2026-05-13): Foundation Hardening — bcrypt
password storage with transparent migration (W4 P1-A), public signup
- ABAC gate (W4 P1-B), credential rotation endpoints (W4 P2-B), user API keys (W4 P3-1/2), risky-coding policy (#8).
- v0.3.0 (2026-05-13 →): Platform Skeleton — PolicyEngine as single source of policy decisions, sandbox capability bridge, OpenSSF silver-tier evidence track: CLA (#340), governance/CoC/roles (#353), bandit SAST gate (#356), security assurance case (PR #360), access continuity + bus factor documented (PR #362).
This software is provided "as is" without warranty of any kind, express or implied. The maintainers are not liable for any damages arising from the use of this software in any context, especially production environments handling sensitive data.
Use this software at your own risk.