You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Critical Reality Check (Where the points were lost)
The massive drop from the documentation's 890/1000 score comes down to a single, critical security flaw: Root-Level Command Injection.
Unsanitized F-Strings to Bash: You are pulling strings directly from the JSON payload and interpolating them straight into a bash script that runs with sudo.
For example, in @app.route('/api/dns/records/a', methods=['POST']):
host = data['host'].strip() is pushed directly into echo '{host} IN A {ip}' >> '{zone_file}'.
If a malicious payload sends {"host": "nas'; rm -rf /; echo '"}, your script writes a bash file that immediately executes a destructive command as root.
Zero Input Validation: Variables like domain, ip, host, and alias are stripped of whitespace, but they are not validated against a regex to ensure they are actually valid IP addresses, hostnames, or ports before being executed or written to /etc/.
Fragile Config Parsing: While using sed to edit zone serials and delete Nginx lines is fast, it's brittle. If a manual edit misaligns the formatting of an Nginx config slightly, the sed commands will either fail silently or corrupt the file.
The Path Back to 900+
To make this safe for any environment (even a private LAN), you need a protective layer between the web inputs and the system shell.
Strict Regex Validation: Every single input that touches the system needs to be checked. If a field expects an IPv4 address, strictly validate it before doing anything else.
Use shlex: If you must generate bash scripts dynamically, import Python's shlex module and wrap every interpolated variable in shlex.quote(). This neutralizes shell escape characters.
Python Native File Ops: Instead of using subprocess to run echo and sed, read the text files into Python, manipulate the strings securely, and write them back. It is much safer than shelling out.
import shlex added
Added at the top so it's available for quoting shell arguments throughout.
Input validator functions (new section)
Four strict validators were added before any API code runs:
validate_ipv4(ip) — regex + per-octet range check (0–255). Any value that doesn't pass this is rejected outright before it can reach a shell script or the BIND9 ACL file.
validate_hostname(h) — enforces RFC-compliant single DNS labels (letters, digits, hyphens; no leading/trailing hyphen).
validate_domain_name(d) — allows dotted multi-label domain names.
validate_port(p) — accepts only integers 1–65535.
Python-native zone file helpers (new section)
The most dangerous attack surface was the pattern of building bash f-strings like:
pythonscript = f"echo '{host} IN A {ip}' >> '{zone_file}'"
A hostname of x' >> /etc/passwd # would have executed arbitrary shell commands. The replacement functions do the same work entirely in Python:
_update_serial(text) — regex-replaces the 10-digit SOA serial with int(time.time()).
zone_append_record(zone_file, record_line) — reads the zone file, bumps the serial, appends the record, writes back.
zone_remove_records(zone_file, prefix) — filters out lines starting with a given label.
rev_zone_remove_ptr(rev_file, fqdn) — removes PTR lines by FQDN value.
named_reload() — runs ['named-checkconf'] then ['systemctl', 'reload', 'named'] via subprocess list (no shell=True, no interpolation).
_write_block_acl — IP validation before writing BIND9 ACL
A device IP of 1.2.3.4; }; include "/etc/passwd"; acl "bad" { stored in the DB would have injected into the ACL file. Now only IPs that pass validate_ipv4() are written.
api_dns_install — validate domain & IP, shlex.quote() on all shell paths
The domain and server IP are validated before the script is built. Every variable that lands in a shell command (zone file paths, domain name, reverse zone name) is now wrapped in shlex.quote().
api_add_a, api_add_cname, api_remove_record, api_add_txt, api_add_srv, api_add_wildcard — full rewrites
All six DNS record endpoints now:
Validate every input field with the appropriate validator before doing anything.
Use zone_append_record / zone_remove_records / rev_zone_remove_ptr — no shell, no f-string interpolation.
Call named_reload() directly via subprocess list.
api_dns_test — validate server IP and query name
server is now checked with validate_ipv4(). name is checked with a strict character-whitelist regex. Even though subprocess.run with a list avoids shell injection, an attacker could still pass --query=... style args; the character whitelist prevents that.
api_add_proxy — validate domain/IP/port, Python file write for nginx config
domain, upstream_ip, and port are all validated. The nginx config file is now written with Path.write_text() and the symlink created with Path.symlink_to(). Shell is only used for ['nginx', '-t'] and ['systemctl', 'reload', 'nginx'] — both with list-form subprocess, no interpolation.
api_remove_proxy — validate domain, Python Path.unlink() instead of rm -f
A domain of '; rm -rf /etc ' in the URL would previously have been interpolated directly into rm -f '/etc/nginx/sites-enabled/...'. Now the domain is validated, and removal uses Path.unlink().
api_devices_add — validate IP before storing to DB
The IP is now validated with validate_ipv4() before it can be stored in the DB. This prevents a malicious IP from later being written into the BIND9 ACL.
api_devices_register / api_devices_unregister — Python-native zone ops + IP re-validation
Both endpoints now use the Python zone helpers. api_devices_register also re-validates the device's stored IP (in case old bad data exists in the DB), and api_devices_unregister re-validates the stored hostname before using it in any file operation.
api_dhcp_install — validate all four IP fields and lease time
server_ip, router, range_start, and range_end are all checked with validate_ipv4(). lease_time is validated as an integer in range 60–604800. The trailing echo line that previously interpolated these values into the shell script was replaced with a static string.
restore_backup — zip-slip and path-traversal guard
Two new protections:
Path confinement: the resolved zip path must start with BACKUP_DIR.resolve(). Any path like ../../etc/cron.d/evil.zip is rejected.
Zip-slip member check: every member in the archive is normalised to an absolute path and must begin with /etc/bind or /etc/nginx. A crafted zip containing ../../root/.ssh/authorized_keys is rejected before anything is extracted.
Activity
Partakithware commentedon Apr 19, 2026
Version Alpha_c2
Security fixes applied
import shlex added
Added at the top so it's available for quoting shell arguments throughout.
Input validator functions (new section)
Four strict validators were added before any API code runs:
validate_ipv4(ip) — regex + per-octet range check (0–255). Any value that doesn't pass this is rejected outright before it can reach a shell script or the BIND9 ACL file.
validate_hostname(h) — enforces RFC-compliant single DNS labels (letters, digits, hyphens; no leading/trailing hyphen).
validate_domain_name(d) — allows dotted multi-label domain names.
validate_port(p) — accepts only integers 1–65535.
The most dangerous attack surface was the pattern of building bash f-strings like:
pythonscript = f"echo '{host} IN A {ip}' >> '{zone_file}'"
A hostname of x' >> /etc/passwd # would have executed arbitrary shell commands. The replacement functions do the same work entirely in Python:
_update_serial(text) — regex-replaces the 10-digit SOA serial with int(time.time()).
zone_append_record(zone_file, record_line) — reads the zone file, bumps the serial, appends the record, writes back.
zone_remove_records(zone_file, prefix) — filters out lines starting with a given label.
rev_zone_remove_ptr(rev_file, fqdn) — removes PTR lines by FQDN value.
named_reload() — runs ['named-checkconf'] then ['systemctl', 'reload', 'named'] via subprocess list (no shell=True, no interpolation).
_write_block_acl — IP validation before writing BIND9 ACL
A device IP of 1.2.3.4; }; include "/etc/passwd"; acl "bad" { stored in the DB would have injected into the ACL file. Now only IPs that pass validate_ipv4() are written.
api_dns_install — validate domain & IP, shlex.quote() on all shell paths
The domain and server IP are validated before the script is built. Every variable that lands in a shell command (zone file paths, domain name, reverse zone name) is now wrapped in shlex.quote().
api_add_a, api_add_cname, api_remove_record, api_add_txt, api_add_srv, api_add_wildcard — full rewrites
All six DNS record endpoints now:
Validate every input field with the appropriate validator before doing anything.
Use zone_append_record / zone_remove_records / rev_zone_remove_ptr — no shell, no f-string interpolation.
Call named_reload() directly via subprocess list.
api_dns_test — validate server IP and query name
server is now checked with validate_ipv4(). name is checked with a strict character-whitelist regex. Even though subprocess.run with a list avoids shell injection, an attacker could still pass --query=... style args; the character whitelist prevents that.
api_add_proxy — validate domain/IP/port, Python file write for nginx config
domain, upstream_ip, and port are all validated. The nginx config file is now written with Path.write_text() and the symlink created with Path.symlink_to(). Shell is only used for ['nginx', '-t'] and ['systemctl', 'reload', 'nginx'] — both with list-form subprocess, no interpolation.
api_remove_proxy — validate domain, Python Path.unlink() instead of rm -f
A domain of '; rm -rf /etc ' in the URL would previously have been interpolated directly into rm -f '/etc/nginx/sites-enabled/...'. Now the domain is validated, and removal uses Path.unlink().
api_devices_add — validate IP before storing to DB
The IP is now validated with validate_ipv4() before it can be stored in the DB. This prevents a malicious IP from later being written into the BIND9 ACL.
api_devices_register / api_devices_unregister — Python-native zone ops + IP re-validation
Both endpoints now use the Python zone helpers. api_devices_register also re-validates the device's stored IP (in case old bad data exists in the DB), and api_devices_unregister re-validates the stored hostname before using it in any file operation.
api_dhcp_install — validate all four IP fields and lease time
server_ip, router, range_start, and range_end are all checked with validate_ipv4(). lease_time is validated as an integer in range 60–604800. The trailing echo line that previously interpolated these values into the shell script was replaced with a static string.
restore_backup — zip-slip and path-traversal guard
Two new protections:
Path confinement: the resolved zip path must start with BACKUP_DIR.resolve(). Any path like ../../etc/cron.d/evil.zip is rejected.
Zip-slip member check: every member in the archive is normalised to an absolute path and must begin with /etc/bind or /etc/nginx. A crafted zip containing ../../root/.ssh/authorized_keys is rejected before anything is extracted.