diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fc6f485 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + # Run weekly on Mondays at midnight UTC to catch upstream changes + - cron: '0 0 * * 1' + +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y swig gcc python3-dev + + - name: Install Python dependencies + run: | + pip install --upgrade pip setuptools pytest + + - name: Clone upstream libinjection + run: | + git clone --depth=1 https://github.com/libinjection/libinjection.git upstream + + - name: Copy upstream source files + run: | + cp -f upstream/src/libinjection*.h upstream/src/libinjection*.c libinjection/ + + - name: Create tests symlink for test_driver.py + # test_driver.py resolves test files relative to ../tests from the repo root + run: | + ln -s "$(realpath upstream/tests)" "$(realpath ..)/tests" + + - name: Generate words.py from upstream data + run: | + python json2python.py < upstream/src/sqlparse_data.json > words.py + + - name: Generate SWIG wrapper + run: | + swig -python -builtin -Wall -Wextra libinjection/libinjection.i + + - name: Build C extension in-place + run: | + python setup.py build_ext --inplace + + - name: Run tests + run: | + pytest test_driver.py -v diff --git a/json2python.py b/json2python.py index 381fc5e..effbee5 100755 --- a/json2python.py +++ b/json2python.py @@ -21,7 +21,9 @@ def lookup(state, stype, keyword): keyword = keyword.decode('latin-1') keyword = keyword.upper() if stype == libinjection.LOOKUP_FINGERPRINT: - if keyword in fingerprints and libinjection.sqli_not_whitelist(state): + # sqli_check_fingerprint calls sqli_blacklist (fingerprint membership + # check) and sqli_not_whitelist (false-positive reduction) internally. + if libinjection.sqli_check_fingerprint(state): return 'F' else: return chr(0) diff --git a/libinjection/libinjection.i b/libinjection/libinjection.i index 2a9b370..9b9ac8f 100644 --- a/libinjection/libinjection.i +++ b/libinjection/libinjection.i @@ -154,5 +154,14 @@ for (i = 0; i < $1_dim0; i++) { } %include "libinjection_error.h" %include "libinjection.h" + +// These functions are declared static in the upstream header and therefore +// cannot be exported as symbols from the compiled extension. Ignore them +// so SWIG does not generate wrappers that produce undefined symbol errors +// at import time. Use sqli_check_fingerprint (non-static) instead. +%ignore libinjection_sqli_reset; +%ignore libinjection_sqli_lookup_word; +%ignore libinjection_sqli_blacklist; +%ignore libinjection_sqli_not_whitelist; %include "libinjection_sqli.h" %include "libinjection_xss.h"