Security Review
This document describes the security measures implemented in NetBox SSL and serves as a review checklist for auditing the plugin's security posture.
Core Security Principles
- No private key storage — The plugin explicitly rejects any input containing private keys. Only public certificate metadata is stored.
- Passive administration — The plugin monitors and inventories certificates. It never deploys, renews, or manages certificates actively.
- Defense in depth — Multiple layers of validation on all input paths.
Security Checklist
| Check | Status | Implementation |
| Private key rejection | Implemented | Broad regex in utils/parser.py — detects RSA, EC, generic PRIVATE KEY headers |
| PEM input size limit | Implemented | MAX_PEM_INPUT_BYTES = 65536 enforced on all import paths |
| Certificate format validation | Implemented | cryptography library X.509 parsing with error handling |
| CSV formula injection prevention | Implemented | _sanitize_csv_value() in utils/export.py — prefixes =+-@\t\r characters |
| Export field allowlist | Implemented | ALLOWED_FIELDS frozenset in utils/export.py — blocks arbitrary getattr() |
| Chain validation depth limit | Implemented | MAX_CHAIN_DEPTH = 10 in utils/chain_validator.py |
SSRF Protection
| Check | Status | Implementation |
| HTTPS-only enforcement | Implemented | utils/url_validation.py — shared across ARI and External Source |
| Private IP blocking | Implemented | Checks literal IPs and DNS-resolved addresses |
| Loopback blocking | Implemented | Blocks localhost, 127.0.0.1, ::1, link-local addresses |
| DNS resolution validation | Implemented | Resolves hostname and checks all returned IPs |
| No redirect following | Implemented | allow_redirects=False on outbound requests |
Authentication & Authorization
| Check | Status | Implementation |
| LoginRequiredMixin on custom views | Implemented | All non-model views require authentication |
.restrict() on all querysets | Implemented | Enforces NetBox ObjectPermission constraints |
has_perm() on all POST endpoints | Implemented | Every @action has explicit permission check |
| Custom permissions | Implemented | import_certificate, renew_certificate, bulk_operations, manage_compliance |
| Credential protection | Implemented | write_only=True on serializers, omitted from GraphQL |
Data Protection
| Check | Status | Implementation |
| No secrets in error messages | Implemented | Generic error responses, internal logging only |
| No private keys in database | Enforced | Parser rejects before any database write |
| Credential resolution | Implemented | env:VAR_NAME pattern — no plaintext in database |
| GraphQL field restriction | Implemented | Explicit field lists, never fields="__all__" |
CI/CD Security
| Check | Status | Implementation |
| Ruff linting | Enabled | Runs on every push/PR |
| Bandit SAST scanning | Enabled | Static analysis for common Python security issues |
| Multi-version testing | Enabled | Python 3.10/3.11/3.12, NetBox 4.4/4.5 |
| Dependabot | Enabled | Automated dependency updates and security alerts |
| Secret scanning | Enabled | GitHub secret scanning + push protection |
Previous Security Hardening
- v0.7.5 (10 findings fixed): LoginRequiredMixin, GraphQL
.restrict(), API permissions, PEM size limits, error sanitization, CSV injection prevention - v0.8.0 (34 findings fixed): SSRF protection, credential exposure prevention, atomic transactions, private key guard on sync engine
- v0.9.0: Shared SSRF util, backward-compatible permissions, ARI HTTPS-only polling