Last Updated: April 30, 2026
This document outlines security considerations, best practices, and threat mitigations in Pluginator.
Pluginator operates with the following security principles:
- Least Privilege - Only requests permissions needed for operation
- Defense in Depth - Multiple layers of validation and protection
- Fail Secure - Errors default to safe/conservative behavior
- No Remote Code Execution - Never evaluates arbitrary code
- Path traversal attacks
- Malicious file downloads
- Configuration injection
- API credential exposure
- File integrity verification
- SSRF via plugin source URLs (v2.8.0+)
- File corruption on crash (atomic write boundary, v2.8.0+)
- Operating system vulnerabilities
- Network-level attacks (MITM without HTTPS)
- Physical access to the server
- Vulnerabilities in downloaded plugins themselves
All file operations use safe path utilities:
// Safe path joining prevents traversal
const result = safePath('/base/dir', '../../../etc/passwd');
// result.valid === false
// result.error === 'Path traversal detected'
// Safe join throws on traversal attempts
safeJoin('/server/plugins', '../../../sensitive'); // Throws ErrorProtected operations:
- Plugin file copying
- Backup creation
- Configuration file access
- Log file writing
Downloaded files have their names sanitized:
sanitizeFilename('plugin<script>.jar'); // 'pluginscript.jar'
sanitizeFilename('../../malicious.jar'); // 'malicious.jar'
sanitizeFilename('file:with:colons.jar'); // 'filewithcolons.jar'Removed characters:
- Path separators (
/,\) - Special characters (
<,>,:,",|,?,*) - Control characters (
\x00-\x1f) - Leading dots
All downloads can be verified against checksums:
const result = await verifyChecksum(filePath, expectedHash, 'sha256');
if (!result.valid) {
// Delete the file, report error
}Supported algorithms:
- SHA-256 (recommended)
- SHA-512
- SHA-1 (legacy)
- MD5 (legacy, not recommended)
All outbound HTTP requests routed through BasePluginSource.fetchJson are validated by src/infrastructure/url-security.ts before connecting. The validator (validateExternalUrl) raises a SecurityError (PLG-8001 through PLG-8004) for any of the following:
- Private IPv4 ranges:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16(link-local / cloud metadata),0.0.0.0/8,127.0.0.0/8 - Private IPv6 ranges:
fc00::/7(unique-local),fe80::/10(link-local),::1(loopback in any form) - IPv4-mapped/compatible IPv6 bypass attempts:
::ffff:x.x.x.x,::x.x.x.x - Numeric IP encodings: hex (
0x7f000001), decimal (2130706433), octal (0177.0.0.1) - Blocked hostnames:
localhost,0.0.0.0,[::], and IPv4-mapped variants - Disallowed protocols: anything other than
http:orhttps:(blocksfile://,ftp://,gopher://, etc.)
This protects against attackers who can influence a source URL (e.g., custom web manifests, Jenkins URLs, theme marketplace URLs, OAuth callback redirects) from probing internal services or cloud metadata endpoints.
All built-in plugin source APIs use HTTPS:
- Spigot/Spiget API
- Modrinth API
- GitHub API
- CurseForge API
- Hangar API (PaperMC)
- GeyserMC API
- Jenkins (HTTPS preferred; HTTP allowed for self-hosted CI)
- Web manifests (HTTP/HTTPS, validated as above)
Strong recommendation: store API tokens in environment variables, never in shell history or config files committed to version control.
# GitHub personal access token (60/hr → 5000/hr rate limit lift)
export GITHUB_TOKEN=ghp_xxxxx
# CurseForge API key (paid tier required)
export CURSEFORGE_API_KEY=$2a$10$xxxxx
# NinSys account token (set automatically by `pluginator login`)
# Stored encrypted in ~/.pluginator/session.jsonToken handling rules (v2.8.0+ guidance):
- CLI flags that take tokens are accepted but discouraged — they leak into shell history and
psoutput - Environment variables take precedence over config files
- If a token must live in a config file, that file should be
chmod 600 - Never commit credentials to version control. Pluginator does not write tokens to logs (logs are sanitized — see Logging Security below).
Session tokens are encrypted at rest to provide defense-in-depth protection:
Encryption Details:
- Algorithm: AES-256-GCM (authenticated encryption)
- Key derivation: PBKDF2 with 100,000 iterations (SHA-256)
- IV: 12 bytes (96 bits) per NIST recommendation
- Auth tag: 16 bytes (128 bits) for tamper detection
Key Material:
- Derived from machine-specific identifier (hostname, username, home directory, architecture)
- Salt stored in
~/.pluginator/.saltwith 0o600 permissions - Key buffers are zeroed after use to minimize memory exposure
Session File Format (v2):
{
"version": 2,
"iv": "<base64-encoded-12-byte-iv>",
"authTag": "<base64-encoded-16-byte-tag>",
"ciphertext": "<base64-encoded-encrypted-session>"
}Threat Mitigations:
| Threat | Mitigation |
|---|---|
| Malware reading session file | Token encrypted, not readable without key |
| Backup exposure | Tokens remain encrypted in backups |
| Session file moved/copied | Encryption tied to machine identity |
| Forensic recovery | Deleted plaintext not recoverable |
| Memory scraping | Key buffers zeroed after use |
Backward Compatibility:
- Plaintext sessions (v1) are still readable
- Automatically upgraded to encrypted (v2) on next save
- No manual migration required
State files that, if corrupted on crash, would leave the user unable to launch Pluginator are written via src/infrastructure/atomic-write.ts (writeFileAtomic / writeFileAtomicSync). The pattern is write-to-.tmp-then-rename, relying on POSIX rename atomicity within a single filesystem.
Files protected by atomic write:
~/.pluginator/config.json~/.pluginator/session.json~/.pluginator/preferences.json~/.pluginator/scan-cache.json~/.pluginator/notifications.json~/.pluginator/schedule.json~/.pluginator/themes/active.json- Operation journal entries
- Server config snapshots
- Sync copy targets (JAR copies write
.tmpthen rename)
Stale .tmp files from prior crashes are cleaned up on startup by cleanStaleTempFiles.
The logger's readOnlyFiles option (which chmod-flipped log files 444↔644 on every write to deter tampering) was changed to default false in v2.11.52 after diagnostic logging revealed the chmod-back race window was causing intermittent EACCES failures during high-frequency log writes — these were being swallowed by upstream .catch(() => []) handlers and silently breaking unrelated workflows (e.g., plugin scans returning 0 plugins). Tamper-resistance of logs at rest was judged not worth the runtime cost. See src/infrastructure/logger.ts for the rationale comment.
CLI Commands:
# Check session encryption status
pluginator auth status
# Manually migrate to encrypted format
pluginator auth migrate
# Clear session (logout)
pluginator auth clearRecommended permissions:
# User data directory
chmod 700 ~/.pluginator/
# Configuration files
chmod 600 ~/.pluginator/config.json
chmod 600 ~/.pluginator/session.json
# Data directories
chmod 700 ~/.pluginator/backups/
chmod 700 ~/.pluginator/logs/
chmod 700 ~/.pluginator/cache/All configuration is validated with Zod schemas:
// Paths are validated
PROD_SERVER_PATH: z.string().min(1)
// Numbers have bounds
MAX_BACKUPS: z.number().int().min(1).max(100)
// URLs are validated
apiUrl: z.string().url()Environment variables override config files, allowing secure CI/CD:
# Override without modifying files
PROD_SERVER_PATH=/secure/path pluginator syncBuilt-in rate limiting prevents abuse:
| API | Rate Limit |
|---|---|
| Spigot (Spiget) | 100 req/min |
| Modrinth | 300 req/min |
| GitHub | 60/hr (5000/hr with token) |
| CurseForge | Varies by key tier |
All HTTP requests have hardcoded timeouts (8-10 seconds) to prevent hanging connections.
Proper identification in requests:
The User-Agent header is dynamically set from package.json version (e.g., Pluginator/2.11.61).
toPluginatorError() in src/core/errors/error-types.ts preserves the original thrown value as the standard ES2022 error.cause property. Native Node errors (e.g., TimeoutError, AbortError, ETIMEDOUT, ENOTFOUND) are classified by their typed name/code first; substring matching is fallback-only. This prevents a misleading "this is a timeout" classification from masking a different underlying error.
Backups are tar.gz archives with predictable structure:
- No symlinks followed (prevents symlink attacks)
- No device files included
- No setuid/setgid bits preserved
Automatic cleanup prevents disk exhaustion. Configure max backup count in ~/.pluginator/config.json.
Backups are stored in ~/.pluginator/backups/ by default.
Logs are sanitized to avoid exposing:
- API tokens
- Passwords
- Full file paths (when possible)
Log files are created daily and automatically cleaned up.
chmod 600 ~/.pluginator/logs/*.log- Minimal dependency footprint
- Regular dependency audits
- No arbitrary code execution (eval, etc.)
All changes undergo review for:
- Input validation
- Path handling
- Error handling
- Credential exposure
Do not report security vulnerabilities publicly.
Contact: Open a private security advisory or reach out via GitHub profile
Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
Response timeline:
- Acknowledgment: 48 hours
- Assessment: 7 days
- Fix (if confirmed): 30 days
- Set restrictive permissions on config files
- Use environment variables for API tokens
- Verify server paths are correct
- Enable HTTPS for custom web manifests
- Keep Pluginator updated
- Review logs for anomalies
- Audit plugin sources periodically
- Verify backup integrity
- Run as non-root user
- Use dedicated service account
- Restrict network access if possible
- Monitor for unauthorized changes
- Plugin Content - Pluginator cannot verify the safety of plugin contents
- Source Trust - Relies on source repositories for plugin safety
- Network Security - Assumes secure network between client and APIs
- Local Access - Cannot protect against malicious local users
Pluginator is designed to support:
- General data protection best practices
- Secure software development lifecycle
- Industry-standard encryption (HTTPS/TLS)
For specific compliance requirements (GDPR, SOC2, etc.), additional measures may be needed at the deployment level.