Docs / Reference
Security model
A findings database is itself a secret store. Perryscope treats it like one — encrypted, masked, audited, and fail-closed.
Authentication & access
X-Periscope-API-Keyheader required on/agent/*; optionally on/scan/*viaPROTECT_SCAN_ROUTES=true- Production guard: startup fails if the API key is unset and
ENVIRONMENT=production - RBAC — 6 roles, 11 permissions — enforced on agent lifecycle actions
- Constant-time API-key comparison; security headers; CORS hardening (wildcard origins disable credentials)
Encryption at rest
- All stored secret and personal-data values encrypted via Fernet (AES) under
PERISCOPE_SECRET_KEY - Multiple keys enable MultiFernet rotation — old values decrypt, new values use the newest key
- Plaintext never serialized in list APIs; display masked by default
- Fail-safe default: unknown storage mode falls back to encrypted
The reveal flow
Only POST /findings/{id}/reveal decrypts a value: written justification required → sliding-window rate limit (10 reveals / 60 s per user) → decrypt in memory → immutable audit entry (who, when, why) → value discarded.
Logging & observability
SecretRedactionFilterscrubs credentials from every log record- Structured JSON logs with correlation IDs across request → scan → finding
- Prometheus metrics; health/liveness/readiness probes
Deterministic decisions
The decision engine is rule-based and reproducible. The optional LLM explainer is architecturally isolated — it can describe a finding in plain English but never influences decisions, scoring, or state transitions.
Source: backend/README.md (Security model) · core/rbac.py · utils/crypto