OpenPLC Runtime v4 implements multiple layers of security to protect against common vulnerabilities and ensure safe operation in industrial environments.
The runtime automatically generates self-signed TLS certificates on first run:
Certificate Files:
webserver/certOPENPLC.pem- Certificatewebserver/keyOPENPLC.pem- Private key
Certificate Details:
- Key size: 2048-bit RSA
- Validity: 365 days
- Subject: CN=localhost
- Subject Alternative Names: localhost, 127.0.0.1, and detected IP addresses
Implementation: webserver/credentials.py
The certificate generation process includes security validations:
- Hostname Validation: Prevents command injection in hostname field
- IP Address Validation: Ensures valid IP format
- Path Validation: Prevents path traversal in certificate file paths
- Automatic Renewal: Checks certificate validity and regenerates if expired
Key Functions:
CertGen.generate_self_signed_cert(cert_file, key_file)
CertGen.is_certificate_valid(cert_file)To use custom certificates (e.g., from Let's Encrypt):
- Replace
webserver/certOPENPLC.pemwith your certificate - Replace
webserver/keyOPENPLC.pemwith your private key - Ensure certificate includes all necessary hostnames/IPs
- Restart the runtime
Certificate Chain: If using a certificate chain, concatenate certificates:
cat your_cert.pem intermediate.pem root.pem > certOPENPLC.pemThe runtime uses JSON Web Tokens (JWT) for authentication:
Token Storage:
- Secret key stored in
/var/run/runtime/.env - 256-bit hexadecimal secret (64 characters)
- Generated automatically on first run
Token Usage:
- WebSocket debug interface requires JWT authentication
- Tokens obtained via REST API login endpoint
- Tokens can be revoked by regenerating secret
Environment Variable:
JWT_SECRET_KEY=<64-character-hex-string>
User passwords are protected with multiple layers:
- Hashing: Passwords hashed using secure algorithm
- Salting: Unique salt per user
- Pepper: Global 256-bit pepper stored in
.env - Database: Stored in
/var/run/runtime/restapi.db
Environment Variable:
PEPPER=<64-character-hex-string>
The .env file is automatically generated with secure defaults:
Location: /var/run/runtime/.env
Contents:
SQLALCHEMY_DATABASE_URI=sqlite:////var/run/runtime/restapi.db
JWT_SECRET_KEY=<auto-generated>
PEPPER=<auto-generated>
Permissions:
- File created with restricted permissions
- Only accessible by runtime user
- Should not be committed to version control
Implementation: webserver/config.py
Uploaded ZIP files undergo comprehensive security checks before extraction:
Checks:
- No absolute paths (starting with
/) - No parent directory references (
..) - No drive letters (
:character) - Extraction paths validated to stay within destination
Example Blocked Paths:
/etc/passwd
../../../etc/passwd
C:\Windows\System32
Limits:
- Maximum file size: 10 MB per file
- Maximum total size: 50 MB uncompressed
- Enforced before extraction
Purpose: Prevent resource exhaustion and disk space attacks
Check: Compression ratio must be ≤ 1000:1
Example:
- Compressed: 1 KB
- Uncompressed: 1 MB (ratio 1000:1) - Allowed
- Uncompressed: 2 MB (ratio 2000:1) - Blocked
Purpose: Prevent decompression bombs that exhaust disk space or memory
Blocked Extensions:
.exe- Windows executables.dll- Dynamic libraries.sh- Shell scripts.bat- Batch files.js- JavaScript files.vbs- VBScript files.scr- Screen savers (often malware)
Purpose: Prevent execution of malicious code
Removed:
__MACOSX/directory.DS_Storefiles
Purpose: Clean up unnecessary metadata that could contain sensitive information
Implementation: webserver/plcapp_management.py
The extraction process includes additional safety measures:
- Root Folder Stripping: Automatically removes single root folder
- Path Normalization: Converts all paths to absolute and validates
- Directory Creation: Creates necessary subdirectories safely
- Atomic Operations: Extraction is all-or-nothing
A device keeps a copy of the source project it is running so it can be retrieved later (see RETRIEVE_PROJECT.md). Its security properties are deliberately narrow, and worth stating plainly.
It is not encrypted. The project is a plain ZIP file under the persistent data directory. Anyone who can read the device's filesystem can read the project -- over SSH, with physical access, from a backup image, or from a mounted volume in a container deployment.
The access control is one role check. GET /api/project-snapshot refuses
the archive to anyone who is not an administrator. That protects the network
path and nothing else; it is not a second layer behind encryption, because there
is no encryption behind it.
There is no integrity guarantee. Nothing signs or checksums the stored project. A project replaced on disk is retrieved as if it were the original, and neither the device nor the client can tell.
The project name is advertised unauthenticated. The UDP discovery reply on port 33333 carries the stored project's name and timestamp so clients can populate a device picker before anyone signs in. Anything on the same network can read them. The archive itself is never served over discovery.
If a deployment needs the project protected at rest, use full-disk or filesystem-level encryption on the device. This feature is not a substitute.
The runtime requires elevated privileges for:
- Real-Time Scheduling: Setting SCHED_FIFO priority
- Socket Creation: Creating Unix domain sockets in
/run/runtime/ - Port Binding: Binding to port 8443 (privileged port)
Recommendation: Run with sudo or grant specific capabilities:
sudo setcap cap_sys_nice,cap_net_bind_service=+ep build/plc_mainSeparate Processes:
- Web server runs as Python process
- PLC runtime runs as separate C/C++ process
- Communication only via Unix domain sockets
Benefits:
- Crash in one process doesn't affect the other
- Different privilege levels possible
- Memory isolation
The runtime handles signals gracefully:
- SIGINT: Graceful shutdown, cleanup resources
- SIGTERM: Graceful shutdown
- SIGSEGV: Caught and logged (if possible)
Implementation: core/src/plc_app/plc_main.c
Default Ports:
- 8443 (HTTPS) - REST API for OpenPLC Editor communication
Recommendations:
- Use firewall to restrict access
- Only expose to trusted networks
- Consider VPN for remote access
Docker Port Mapping:
docker run -p 127.0.0.1:8443:8443 ... # Localhost only
docker run -p 8443:8443 ... # All interfacesSocket Locations:
/run/runtime/plc_runtime.socket- Command socket/run/runtime/log_runtime.socket- Log socket
Security:
- Only accessible on local system
- File system permissions control access
- No network exposure
Permissions:
- Created with restricted permissions
- Only runtime user can connect
Location: /var/run/runtime/restapi.db
Contents:
- User accounts
- Hashed passwords
- Session data
Protection:
- File system permissions
- SQLite encryption (if configured)
- Regular backups recommended
Sensitive Information:
- Logs may contain variable values
- IP addresses and connection information
- Error messages with file paths
Recommendations:
- Rotate logs regularly
- Restrict log file access
- Sanitize logs before sharing
Location: build/libplc_*.so
Contents:
- Compiled PLC logic
- Variable definitions
- Custom function blocks
Protection:
- File system permissions
- Not transmitted over network
- Timestamped for version control
Benefits:
- Process isolation from host
- Network isolation (unless exposed)
- File system isolation
Considerations:
- Privileged mode may be needed for real-time scheduling
- Volume mounts expose host directories
- Port exposure creates network access
Persistent Volume:
docker run -v openplc-runtime-data:/var/run/runtime ...Contents:
.envfile with secrets- Database with user accounts
- Compiled programs
Recommendations:
- Use named volumes (not bind mounts)
- Backup volumes regularly
- Restrict volume access
Official Image: ghcr.io/autonomy-logic/openplc-runtime:latest
Security Features:
- Based on Debian bookworm-slim (minimal attack surface)
- Regular updates via CI/CD
- Multi-architecture support
- Signed images (GitHub Container Registry)
Verification:
docker pull ghcr.io/autonomy-logic/openplc-runtime:latest
docker inspect ghcr.io/autonomy-logic/openplc-runtime:latest- Use HTTPS Only: Never disable TLS
- Strong Passwords: Enforce password complexity
- Regular Updates: Keep runtime and dependencies updated
- Firewall Rules: Restrict network access
- Monitoring: Monitor logs for suspicious activity
- Code Review: Review all changes before merging
- Static Analysis: Use linters and security scanners
- Dependency Scanning: Check for vulnerable dependencies
- Input Validation: Validate all user inputs
- Error Handling: Don't expose sensitive information in errors
- Backup Regularly: Backup
/var/run/runtime/directory - Rotate Secrets: Regenerate JWT secret and pepper periodically
- Audit Logs: Review logs for security events
- Update Certificates: Renew certificates before expiration
- Least Privilege: Run with minimum required privileges
If you discover a security vulnerability:
- Do Not open a public issue
- Contact the maintainers privately
- Provide detailed reproduction steps
- Allow time for patch development
- Coordinate disclosure timing
- Self-Signed Certificates: Default certificates are self-signed (OpenPLC Editor handles this automatically)
- No Rate Limiting: API endpoints not rate-limited
- No Account Lockout: No protection against brute force
- No Audit Trail: Limited logging of security events
- No Encryption at Rest: Database and files not encrypted
Planned security enhancements:
- Rate limiting for API endpoints
- Account lockout after failed attempts
- Comprehensive audit logging
- Database encryption at rest
- Certificate management UI
- Two-factor authentication
- Role-based access control
The runtime is designed for industrial automation but does not claim compliance with specific standards (e.g., IEC 62443). Organizations should:
- Conduct security assessments
- Implement additional controls as needed
- Follow industry-specific guidelines
- Document security configurations
For environments with data protection requirements (GDPR, etc.):
- Minimize data collection
- Implement data retention policies
- Provide data export capabilities
- Document data flows
- Implement access controls
- Editor Integration - How OpenPLC Editor connects to runtime
- Architecture - System overview
- API Reference - REST endpoints
- Debug Protocol - WebSocket interface
- Docker Deployment - Container security
- Troubleshooting - Security-related issues