diff --git a/docker-security/configure.py b/docker-security/configure.py
new file mode 100644
index 0000000..ac3b304
--- /dev/null
+++ b/docker-security/configure.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+"""Conservative XML edits preserving unrelated Wazuh configuration/comments."""
+from pathlib import Path
+import re
+import sys
+from xml.dom import minidom
+
+LOG = '/var/log/wazuh-docker-security/events.jsonl'
+
+
+def parse_fragment(text):
+ if '', '', text, count=1)
+ return minidom.parseString('' + text + '')
+
+
+def element_children(parent, tag):
+ return [n for n in parent.childNodes if n.nodeType == n.ELEMENT_NODE and n.tagName == tag]
+
+
+def value(node):
+ return ''.join(n.data for n in node.childNodes if n.nodeType == n.TEXT_NODE).strip()
+
+
+def set_child(document, parent, tag, text):
+ nodes = element_children(parent, tag)
+ if len(nodes) > 1:
+ raise ValueError('ambiguous duplicate ' + tag)
+ node = nodes[0] if nodes else document.createElement(tag)
+ if not nodes:
+ parent.appendChild(node)
+ for child in list(node.childNodes):
+ node.removeChild(child)
+ node.appendChild(document.createTextNode(text))
+
+
+def configure(text, listener, scans):
+ doc = parse_fragment(text)
+ roots = element_children(doc.documentElement, 'ossec_config')
+ if not roots:
+ raise ValueError('ossec_config missing')
+ if listener:
+ matches = [n for root in roots for n in element_children(root, 'wodle')
+ if n.getAttribute('name') == 'docker-listener']
+ if len(matches) > 1:
+ raise ValueError('multiple docker-listener blocks; resolve manually')
+ node = matches[0] if matches else doc.createElement('wodle')
+ if not matches:
+ node.setAttribute('name', 'docker-listener')
+ roots[0].appendChild(node)
+ set_child(doc, node, 'disabled', 'no')
+ if scans:
+ matches = [n for root in roots for n in element_children(root, 'localfile')
+ if any(value(x) == LOG for x in element_children(n, 'location'))]
+ if len(matches) > 1:
+ raise ValueError('multiple managed scan log blocks')
+ node = matches[0] if matches else doc.createElement('localfile')
+ if not matches:
+ roots[0].appendChild(doc.createComment(' Managed by wazuh-docker-security '))
+ roots[0].appendChild(node)
+ set_child(doc, node, 'location', LOG)
+ set_child(doc, node, 'log_format', 'json')
+ set_child(doc, node, 'only-future-events', 'no')
+ return ''.join(n.toxml() for n in doc.documentElement.childNodes).rstrip() + '\n'
+
+
+def check_rule_collisions(rules_dir, target, new_rules):
+ wanted = set(re.findall(r']*\bid\s*=[\'"](\d+)[\'"]', path.read_text()))
+ if wanted & existing:
+ raise ValueError('rule IDs already used in ' + str(path))
+
+
+def main():
+ mode = sys.argv[1]
+ if mode == 'agent':
+ source, output = map(Path, sys.argv[2:4])
+ if source.is_symlink() or not source.is_file():
+ raise ValueError('ossec.conf must be a regular file')
+ output.write_text(configure(source.read_text(), sys.argv[4] == '1', sys.argv[5] == '1'))
+ elif mode == 'rules':
+ source, target = map(Path, sys.argv[2:4])
+ new_rules = source.read_text()
+ parse_fragment(new_rules)
+ if target.is_symlink():
+ raise ValueError('rule file is a symlink')
+ if target.exists() and 'Managed by wazuh-docker-security' not in target.read_text():
+ raise ValueError('unmanaged rule file already exists')
+ check_rule_collisions(target.parent, target, new_rules)
+ else:
+ raise ValueError('unknown mode')
+
+
+if __name__ == '__main__':
+ main()