dgtlmoon/changedetection.io · error · ValueError
Zip Slip path traversal detected in backup archive: {member.
Error message
Zip Slip path traversal detected in backup archive: {member.filename!r} What it means
After stripping the 'json:' prefix, the expression is parsed with jsonpath_ng.ext.parse. If it raises JsonPathParserError or JsonPathLexerError the validator rejects the input with this message including the parser error text. It means the string is not a valid JSONPath expression per the jsonpath-ng extended syntax.
Source
Thrown at changedetectionio/blueprint/backups/restore.py:74
skipped_watches = 0
current_tags = datastore.data['settings']['application'].get('tags', {})
current_watches = datastore.data['watching']
with tempfile.TemporaryDirectory() as tmpdir:
logger.debug(f"Restore: extracting zip to {tmpdir}")
with zipfile.ZipFile(zip_stream, 'r') as zf:
total_uncompressed = sum(m.file_size for m in zf.infolist())
if total_uncompressed > _MAX_DECOMPRESSED_BYTES:
raise ValueError(
f"Backup archive decompressed size ({total_uncompressed // (1024 * 1024)} MB) "
f"exceeds the {_MAX_DECOMPRESSED_BYTES // (1024 * 1024)} MB limit"
)
resolved_dest = os.path.realpath(tmpdir)
for member in zf.infolist():
member_dest = os.path.realpath(os.path.join(resolved_dest, member.filename))
if not member_dest.startswith(resolved_dest + os.sep) and member_dest != resolved_dest:
raise ValueError(f"Zip Slip path traversal detected in backup archive: {member.filename!r}")
zf.extract(member, tmpdir)
logger.debug("Restore: zip extracted, scanning UUID directories")
for entry in os.scandir(tmpdir):
if not entry.is_dir():
continue
uuid = entry.name
if not _UUID_RE.match(uuid):
logger.warning(f"Restore: skipping non-UUID directory {uuid!r}")
continue
tag_json_path = os.path.join(entry.path, 'tag.json')
watch_json_path = os.path.join(entry.path, 'watch.json')
# --- Tags (groups) ---
if include_groups and os.path.exists(tag_json_path):
if uuid in current_tags and not include_groups_replace:
logger.debug(f"Restore: skipping existing group {uuid} (replace not requested)")View on GitHub (pinned to 5d9c7c6da7)
Solutions
- Validate in a REPL: from jsonpath_ng.ext import parse; parse('$.foo[*].bar')
- Ensure the path starts with $ or a bracket expression and brackets/quotes are balanced
- Use JSONPath testers and remember the extended syntax jsonpath-ng supports
- Don't mix jq pipe syntax into json: lines — use jq: prefix instead
Example fix
# before json:.store.book[0].title # after json:$.store.book[0].title
Defensive patterns
Strategy: validation
Validate before calling
from jsonpath_ng.ext import parse
from jsonpath_ng.exceptions import JsonPathParserError, JsonPathLexerError
def jsonpath_ok(expr: str) -> bool:
try:
parse(expr)
return True
except (JsonPathParserError, JsonPathLexerError):
return False Type guard
def is_valid_jsonpath(expr: str) -> bool:
try:
parse(expr)
return True
except (JsonPathParserError, JsonPathLexerError):
return False Try / catch
try:
parse(expr)
except (JsonPathParserError, JsonPathLexerError) as e:
# e.message / str(e) pinpoints the offending token
... Prevention
- Always start JSONPath with $
- Don't mix jq pipe syntax into json: expressions
- Test paths with jsonpath_ng in a REPL or an online JSONPath tester
When it happens
Trigger: Submitting 'json:' lines with malformed JSONPath such as 'json:$[', 'json:.foo..[' (bad recursion/filter syntax), unknown extensions, or unbalanced brackets/quotes that fail jsonpath_ng.ext.parse().
Common situations: Confusing JSONPath with jq syntax (e.g. 'json:.foo | .bar'); missing the leading $ ; bad filter expressions like $[?(@.a ==)]; typos from hand-writing paths instead of copying from a JSONPath tester.
Related errors
- Backup archive decompressed size ({total_uncompressed // (10
- One of the 'conditions' rulesets is incomplete, cannot run.
- BrowserStepsStepException
- Invalid JSON object for field: {value}
- Non200ErrorCodeReceived
AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27).
Data as JSON: /api/errors/a0a86a4942051b15.
Report an issue: GitHub.