From 9cbe9b47a76b40c8eb81d515b854739df59c3142 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:23:18 +0000 Subject: [PATCH 1/3] Initial plan From 59f57fc8240b624557a27378e1d161a3ea3a018c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:26:01 +0000 Subject: [PATCH 2/3] Improve anonymization with HMAC and device-specific salt Co-authored-by: gsanchietti <804596+gsanchietti@users.noreply.github.com> --- src/nethsec/inventory/__init__.py | 57 ++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/src/nethsec/inventory/__init__.py b/src/nethsec/inventory/__init__.py index 2901f8ea..c1f7ab1e 100644 --- a/src/nethsec/inventory/__init__.py +++ b/src/nethsec/inventory/__init__.py @@ -641,11 +641,60 @@ def info_default_ipv6(uci: EUci): return anonmyze(ipv6, uci) -def anonmyze(input, uci: EUci): +def _get_device_salt(): + """ + Get a device-specific salt for anonymization. + Uses /etc/machine-id if available, otherwise falls back to a generated UUID stored in /tmp. + This provides a per-install salt that prevents external precomputation of hashes. + """ + # Try to read machine-id (standard on most Linux systems including OpenWrt) + try: + with open('/etc/machine-id', 'r') as f: + machine_id = f.read().strip() + if machine_id: + return machine_id.encode() + except (FileNotFoundError, IOError): + pass + + # Fallback: use a generated salt stored in /tmp (will persist during device uptime) + salt_file = '/tmp/nethsec-anon-salt' + try: + with open(salt_file, 'r') as f: + salt = f.read().strip() + if salt: + return salt.encode() + except (FileNotFoundError, IOError): + pass + + # Generate new salt and store it + try: + import uuid + salt = str(uuid.uuid4()) + with open(salt_file, 'w') as f: + f.write(salt) + return salt.encode() + except: + # Last resort: use a hardcoded value (not ideal but better than nothing) + return b'nethsec-default-salt' + +def anonmyze(value, uci: EUci): + """ + Anonymize sensitive data using HMAC with a device-specific salt. + + Args: + value: The value to anonymize (e.g., IP address, hostname) + uci: EUci instance for checking subscription status + + Returns: + The original value if subscribed, otherwise an anonymized version + """ if fact_subscription_status(uci).get('status', 'no') != "no": - return input - h = hashlib.sha1(input.encode()).hexdigest() - return f"anon-{h[:16]}" + return value + + # Use HMAC with device-specific salt for secure anonymization + salt = _get_device_salt() + h = hashlib.pbkdf2_hmac('sha256', value.encode(), salt, 100000, dklen=16) + return f"anon-{h.hex()}" def info_package_updates_available(uci: EUci): """Check if package updates are available""" From c79962151c723674a3ce2a60803ad91d7bd38948 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:27:13 +0000 Subject: [PATCH 3/3] Address code review: move imports, use named constant, specify exceptions Co-authored-by: gsanchietti <804596+gsanchietti@users.noreply.github.com> --- src/nethsec/inventory/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/nethsec/inventory/__init__.py b/src/nethsec/inventory/__init__.py index c1f7ab1e..6c223c3d 100644 --- a/src/nethsec/inventory/__init__.py +++ b/src/nethsec/inventory/__init__.py @@ -15,6 +15,10 @@ import configparser import json import hashlib +import uuid + +# Constants for anonymization +PBKDF2_ITERATIONS = 100000 # Number of iterations for PBKDF2-HMAC # run a bash command and return the error code def _run_status(cmd): @@ -668,12 +672,11 @@ def _get_device_salt(): # Generate new salt and store it try: - import uuid salt = str(uuid.uuid4()) with open(salt_file, 'w') as f: f.write(salt) return salt.encode() - except: + except (OSError, IOError): # Last resort: use a hardcoded value (not ideal but better than nothing) return b'nethsec-default-salt' @@ -693,7 +696,7 @@ def anonmyze(value, uci: EUci): # Use HMAC with device-specific salt for secure anonymization salt = _get_device_salt() - h = hashlib.pbkdf2_hmac('sha256', value.encode(), salt, 100000, dklen=16) + h = hashlib.pbkdf2_hmac('sha256', value.encode(), salt, PBKDF2_ITERATIONS, dklen=16) return f"anon-{h.hex()}" def info_package_updates_available(uci: EUci):