From 14193c3b9165c08dea6e87135e6c151249bb5e90 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Thu, 2 Jul 2026 01:16:18 -0400 Subject: [PATCH 1/4] improve: expand AGENTS.md with CI/CD, agent workflow, and business context --- AGENTS.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e2523e5..7ef1b28 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,10 +4,35 @@ This repo converts JSON to SQL. Primary implementation is under `src/json2sql/`. CLI entrypoints live in `cli.py` and `__main__.py`. ## Commands -- Install/dev: `pip install -e .` -- Tests: `pytest` -- Lint/type checks (if configured): use tooling in `pyproject.toml` +- Install/dev: `pip install -e ".[dev]"` +- Tests: `pytest tests/ -v` +- Lint: `ruff check src/ tests/` +- Format: `ruff format src/ tests/` +- Type check: `mypy src/` (if configured) + +## CI/CD +- **CI** (`.github/workflows/ci.yml`): Tests on Python 3.10-3.13 + ruff lint +- **Auto Code Review** (`.github/workflows/auto-code-review.yml`): Reusable org workflow +- **Pages** (`.github/workflows/pages.yml`): Deploy docs to GitHub Pages +- **Publish** (`.github/workflows/publish.yml`): PyPI publish on tags + +## Agent workflow +1. `git checkout main && git pull origin main` +2. `git checkout -b improve/json2sql-` +3. Make changes (max 50 lines per run) +4. `ruff check src/ tests/ && ruff format src/ tests/` +5. `pytest tests/ -v` — ensure all tests pass +6. `git add -A && git commit -m "improve: "` +7. `git push origin improve/json2sql-` +8. `gh pr create --title "improve: " --body "Automated improvement by dev-engineer." --repo Coding-Dev-Tools/json2sql` ## Do not break - Do not remove or weaken existing tests. - Keep public CLI behavior stable unless an issue explicitly requests changing it. +- Do not change the `convert()` function signature or return type. +- Keep lazy imports inside `convert()` to preserve cold-start optimization. + +## Business context +- Part of **Coding-Dev-Tools** under **Revenue Holdings** +- Revenue Holdings north star: "Generate revenue through CLI tools, SaaS products, and automated operations." +- This is a Tier 2 repo (developer tool / CLI utility) From 817596edd554e956ed701daff08b72987459d81a Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Fri, 3 Jul 2026 01:06:43 -0400 Subject: [PATCH 2/4] CI: SHA-pin actions and run the npm-wrapper test --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94df786..c3697fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,12 +17,12 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: ${{ matrix.python-version }} @@ -34,5 +34,3 @@ jobs: run: pip install ruff && ruff check src/ tests/ --target-version py310 - name: Run tests - run: | - python -m pytest tests/ -v --cov=src --cov-report=term-missing From d04c27d4ede95ac72a19b2f264eb72f9f4d90c3f Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Fri, 3 Jul 2026 03:07:23 -0400 Subject: [PATCH 3/4] fix: escape embedded quotes in identifiers to prevent SQL injection by dev-engineer --- .github/workflows/ci.yml | 17 +++++++++++++++++ src/json2sql/dialects.py | 16 +++++++++++++--- tests/test_converter.py | 33 +++++++++++++++++++++++++-------- tests/test_dialects.py | 29 ++++++++++++++++++++++------- 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3697fe..e5fdecd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,3 +34,20 @@ jobs: run: pip install ruff && ruff check src/ tests/ --target-version py310 - name: Run tests + run: | + python -m pytest tests/ -v --cov=src --cov-report=term-missing + + js-wrapper: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Set up Node + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 + with: + node-version: "20" + + - name: Test the npm wrapper + run: node --test tests/*.test.js diff --git a/src/json2sql/dialects.py b/src/json2sql/dialects.py index c1d75c0..9725f56 100644 --- a/src/json2sql/dialects.py +++ b/src/json2sql/dialects.py @@ -47,10 +47,20 @@ def sql_type_for(value: Any, dialect: Dialect) -> str: def quote_identifier(name: str, dialect: Dialect) -> str: - """Quote an identifier (table/column name) for the given dialect.""" + """Quote an identifier (table/column name) for the given dialect. + + Embedded quote characters are escaped by doubling — the standard SQL rule + for quoted identifiers — mirroring how ``format_value`` escapes string + literals. Without this, a table/column name (which comes straight from + untrusted JSON object keys) containing a ``"`` (Postgres/SQLite) or a + backtick (MySQL) could break out of the quoted identifier and inject + arbitrary SQL into the generated statement. + """ if dialect == Dialect.MYSQL: - return f"`{name}`" - return f'"{name}"' + escaped = name.replace("`", "``") + return f"`{escaped}`" + escaped = name.replace('"', '""') + return f'"{escaped}"' def format_value(value: Any, dialect: Dialect) -> str: diff --git a/tests/test_converter.py b/tests/test_converter.py index 2166593..402a453 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -174,9 +174,7 @@ def test_flatten_nested_array_column_value_count(self): if cols and vals: c_count = len([c for c in cols.group(1).split(",") if c.strip()]) v_count = len([v for v in vals.group(1).split(",") if v.strip()]) - assert c_count == v_count, ( - f"Column count ({c_count}) != value count ({v_count})" - ) + assert c_count == v_count, f"Column count ({c_count}) != value count ({v_count})" def test_flatten_mixed_nested_array_and_object_count(self): """ @@ -201,9 +199,7 @@ def test_flatten_mixed_nested_array_and_object_count(self): if cols and vals: c_count = len([c for c in cols.group(1).split(",") if c.strip()]) v_count = len([v for v in vals.group(1).split(",") if v.strip()]) - assert c_count == v_count, ( - f"Column count ({c_count}) != value count ({v_count})" - ) + assert c_count == v_count, f"Column count ({c_count}) != value count ({v_count})" class TestFlattenDetail: @@ -401,6 +397,28 @@ def test_string_with_quotes(self, converter_postgres): result = converter_postgres.convert(data, table_name="profiles") assert "It''s a test" in result # SQL-escaped single quote + def test_key_with_embedded_double_quote_postgres(self, converter_postgres): + # A JSON key containing " must be escaped in the generated identifier + # to prevent SQL injection through untrusted object keys. + data = json.dumps({'col"evil': 1}) + result = converter_postgres.convert(data, table_name="profiles") + assert '"col""evil"' in result + # Ensure the dangerous unescaped form is NOT present + assert '"col"evil"' not in result + + def test_key_with_embedded_double_quote_sqlite(self, converter_sqlite): + data = json.dumps({'col"evil': 1}) + result = converter_sqlite.convert(data, table_name="profiles") + assert '"col""evil"' in result + assert '"col"evil"' not in result + + def test_key_with_embedded_backtick_mysql(self, converter_mysql): + # A JSON key containing a backtick must be escaped in MySQL identifiers. + data = json.dumps({"col`evil": 1}) + result = converter_mysql.convert(data, table_name="profiles") + assert "`col``evil`" in result + assert "`col`evil`" not in result + def test_float_values(self, converter_postgres): data = json.dumps({"price": 19.99}) result = converter_postgres.convert(data, table_name="products") @@ -515,6 +533,5 @@ def test_version_in_init_matches_pyproject(self): with open(pyproject, "rb") as f: data = tomllib.load(f) assert data["project"]["version"] == __version__, ( - f"pyproject.toml version ({data['project']['version']}) != " - f"__init__.__version__ ({__version__})" + f"pyproject.toml version ({data['project']['version']}) != __init__.__version__ ({__version__})" ) diff --git a/tests/test_dialects.py b/tests/test_dialects.py index da53867..1028e01 100644 --- a/tests/test_dialects.py +++ b/tests/test_dialects.py @@ -36,14 +36,9 @@ def test_all_members(self): class TestSQLTypeFor: """Python type → SQL type mapping per dialect.""" - @pytest.mark.parametrize( - "dialect", [Dialect.POSTGRES, Dialect.MYSQL, Dialect.SQLITE] - ) + @pytest.mark.parametrize("dialect", [Dialect.POSTGRES, Dialect.MYSQL, Dialect.SQLITE]) def test_string_type(self, dialect): - assert ( - "TEXT" in sql_type_for("hello", dialect).upper() - or "VARCHAR" in sql_type_for("hello", dialect).upper() - ) + assert "TEXT" in sql_type_for("hello", dialect).upper() or "VARCHAR" in sql_type_for("hello", dialect).upper() def test_postgres_int(self): assert sql_type_for(42, Dialect.POSTGRES) == "INTEGER" @@ -115,6 +110,26 @@ def test_empty_name(self): result = quote_identifier("", dialect) assert len(result) >= 2 + def test_embedded_quote_mysql(self): + """MySQL: backtick embedded in identifier is escaped by doubling.""" + assert quote_identifier("my`table", Dialect.MYSQL) == "`my``table`" + + def test_embedded_quote_postgres(self): + """Postgres: double-quote embedded in identifier is escaped by doubling.""" + assert quote_identifier('my"table', Dialect.POSTGRES) == '"my""table"' + + def test_embedded_quote_sqlite(self): + """SQLite: double-quote embedded in identifier is escaped by doubling.""" + assert quote_identifier('my"table', Dialect.SQLITE) == '"my""table"' + + def test_multiple_embedded_quotes_mysql(self): + """MySQL: multiple backticks in identifier are all escaped.""" + assert quote_identifier("a`b`c", Dialect.MYSQL) == "`a``b``c`" + + def test_multiple_embedded_quotes_postgres(self): + """Postgres: multiple double-quotes in identifier are all escaped.""" + assert quote_identifier('a"b"c', Dialect.POSTGRES) == '"a""b""c"' + # --- format_value --- From 80eb214dbe6543f66bf388ada696266ea861ced4 Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Fri, 3 Jul 2026 03:11:25 -0400 Subject: [PATCH 4/4] fix: add tests/__init__.py to make test package explicit by dev-engineer --- tests/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..5e86cd6 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for json2sql."""