mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-04-30 14:50:39 +00:00
e9e8c8d218
Build and push containers / metadata (push) Has been cancelled
Build and push containers / build-push-containers (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (alpine) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/amd64 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v7 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm/v8 (main) (push) Has been cancelled
ChangeDetection.io Container Build Test / Build linux/arm64 (main) (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / lint-translations (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-14 (push) Has been cancelled
95 lines
3.1 KiB
Python
95 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# run from dir above changedetectionio/ dir
|
|
# python3 -m unittest changedetectionio.tests.unit.test_jq_security
|
|
|
|
import unittest
|
|
|
|
|
|
class TestJqExpressionSecurity(unittest.TestCase):
|
|
|
|
def test_blocked_builtins_raise(self):
|
|
"""Each dangerous builtin must be rejected by validate_jq_expression."""
|
|
from changedetectionio.html_tools import validate_jq_expression
|
|
|
|
blocked = [
|
|
# env access
|
|
'env',
|
|
'.foo | env',
|
|
'$ENV',
|
|
'$ENV.SECRET',
|
|
# file read via module system
|
|
'include "foo"',
|
|
'import "foo" as f',
|
|
# stdin reads
|
|
'input',
|
|
'inputs',
|
|
'[.,inputs]',
|
|
# process termination
|
|
'halt',
|
|
'halt_error(1)',
|
|
# stderr/debug leakage
|
|
'debug',
|
|
'. | debug | .foo',
|
|
'stderr',
|
|
# misc info leakage
|
|
'$__loc__',
|
|
'builtins',
|
|
'modulemeta',
|
|
'$JQ_BUILD_CONFIGURATION',
|
|
]
|
|
|
|
for expr in blocked:
|
|
with self.assertRaises(ValueError, msg=f"Expected ValueError for: {expr!r}"):
|
|
validate_jq_expression(expr)
|
|
|
|
def test_safe_expressions_pass(self):
|
|
"""Normal jq expressions must not be blocked."""
|
|
from changedetectionio.html_tools import validate_jq_expression
|
|
|
|
safe = [
|
|
'.foo',
|
|
'.items[] | .price',
|
|
'map(select(.active)) | length',
|
|
'.[] | select(.name | test("foo"))',
|
|
'to_entries | map(.value) | add',
|
|
'[.[] | .id] | unique',
|
|
'.price | tonumber',
|
|
'if .stock > 0 then "in stock" else "out of stock" end',
|
|
]
|
|
|
|
for expr in safe:
|
|
try:
|
|
validate_jq_expression(expr)
|
|
except ValueError as e:
|
|
self.fail(f"validate_jq_expression raised ValueError for safe expression {expr!r}: {e}")
|
|
|
|
def test_allow_risky_env_var_bypasses_check(self):
|
|
"""JQ_ALLOW_RISKY_EXPRESSIONS=true must skip all blocking."""
|
|
import os
|
|
from unittest.mock import patch
|
|
from changedetectionio.html_tools import validate_jq_expression
|
|
|
|
with patch.dict(os.environ, {'JQ_ALLOW_RISKY_EXPRESSIONS': 'true'}):
|
|
# Should not raise even for the most dangerous expression
|
|
try:
|
|
validate_jq_expression('env')
|
|
validate_jq_expression('$ENV')
|
|
except ValueError as e:
|
|
self.fail(f"Should not block when JQ_ALLOW_RISKY_EXPRESSIONS=true: {e}")
|
|
|
|
def test_allow_risky_env_var_off_by_default(self):
|
|
"""Without JQ_ALLOW_RISKY_EXPRESSIONS set, blocking must be active."""
|
|
import os
|
|
from unittest.mock import patch
|
|
from changedetectionio.html_tools import validate_jq_expression
|
|
|
|
env = {k: v for k, v in os.environ.items() if k != 'JQ_ALLOW_RISKY_EXPRESSIONS'}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with self.assertRaises(ValueError):
|
|
validate_jq_expression('env')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|