Skip to content

Commit e379865

Browse files
committed
Update python-driver security and formatting
Note: This PR was created with AI tools and a human. - Add parameterized query construction using psycopg.sql to prevent SQL injection in all Cypher execution paths (age.py, networkx/lib.py) - Replace all %-format and f-string SQL in networkx/lib.py with sql.Identifier() for schema/table names and sql.Literal() for values - Add validate_graph_name() with AGE-aligned VALID_GRAPH_NAME regex: start with letter/underscore, allow dots and hyphens in middle positions, end with letter/digit/underscore, min 3 chars, max 63 chars - Add validate_identifier() with strict VALID_IDENTIFIER regex for labels, column names, and SQL types (no dots or hyphens) - Add validation calls to all networkx/lib.py entry points: graph names validated on entry, labels validated before SQL construction - Add _validate_column() to sanitize column specifications in buildCypher() - Fix exception constructors (AgeNotSet, GraphNotFound, GraphAlreadyExists) to always call super().__init__() with a meaningful default message so that str(exception) never returns an empty string - Add InvalidGraphName and InvalidIdentifier exception classes with structured name/reason/context fields - Fix builder.py: change erroneous 'return Exception(...)' to 'raise ValueError(...)' for unknown float expressions - Fix copy-paste docstring in create_elabel() ('create_vlabels' -> 'create_elabels') - Remove unused 'from psycopg.adapt import Loader' import in age.py - Add design documentation in source explaining: - VALID_GRAPH_NAME regex uses '*' (not '+') intentionally so that the min-length check fires first with a clear error message - buildCypher uses string concatenation (not sql.Identifier) because column specs are pre-validated 'name type' pairs that don't map to sql.Identifier(); graphName and cypherStmt are NOT embedded - Update test_networkx.py GraphNotFound assertion to use assertIn() instead of assertEqual() to match the improved exception messages - Strip Windows carriage returns (^M) from 7 source files - Fix requirements.txt: convert from UTF-16LE+BOM+CRLF to clean UTF-8+LF, move --no-binary flag from requirements.txt to CI workflow pip command - Upgrade actions/setup-python from v4 (deprecated) to v5 in CI workflow - Add 46 security unit tests in test_security.py covering: - Graph name validation (AGE naming rules, injection, edge cases) - SQL identifier validation (labels, columns, types) - Column spec sanitization - buildCypher injection prevention - Exception constructor correctness (str() never empty) - Add test_security.py to CI pipeline (python-driver.yaml) - pip-audit: 0 known vulnerabilities in all dependencies modified: .github/workflows/python-driver.yaml modified: drivers/python/age/VERSION.py modified: drivers/python/age/__init__.py modified: drivers/python/age/age.py modified: drivers/python/age/builder.py modified: drivers/python/age/exceptions.py modified: drivers/python/age/models.py modified: drivers/python/age/networkx/lib.py modified: drivers/python/requirements.txt modified: drivers/python/setup.py modified: drivers/python/test_agtypes.py modified: drivers/python/test_networkx.py new file: drivers/python/test_security.py
1 parent 887564d commit e379865

13 files changed

Lines changed: 1481 additions & 1005 deletions

File tree

.github/workflows/python-driver.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ jobs:
2222
run: docker compose up -d
2323

2424
- name: Set up python
25-
uses: actions/setup-python@v4
25+
uses: actions/setup-python@v5
2626
with:
2727
python-version: '3.12'
2828

2929
- name: Install pre-requisites
3030
run: |
3131
sudo apt-get install python3-dev libpq-dev
32-
pip install -r requirements.txt
32+
pip install --no-binary psycopg -r requirements.txt
3333
3434
- name: Build
3535
run: |
@@ -40,3 +40,4 @@ jobs:
4040
python test_age_py.py -db "postgres" -u "postgres" -pass "agens"
4141
python test_networkx.py -db "postgres" -u "postgres" -pass "agens"
4242
python -m unittest -v test_agtypes.py
43+
python -m unittest -v test_security.py

drivers/python/age/VERSION.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
# Licensed to the Apache Software Foundation (ASF) under one
2-
# or more contributor license agreements. See the NOTICE file
3-
# distributed with this work for additional information
4-
# regarding copyright ownership. The ASF licenses this file
5-
# to you under the Apache License, Version 2.0 (the
6-
# "License"); you may not use this file except in compliance
7-
# with the License. You may obtain a copy of the License at
8-
# http://www.apache.org/licenses/LICENSE-2.0
9-
# Unless required by applicable law or agreed to in writing,
10-
# software distributed under the License is distributed on an
11-
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
12-
# KIND, either express or implied. See the License for the
13-
# specific language governing permissions and limitations
14-
# under the License.
15-
16-
17-
18-
VER_MAJOR = 1
19-
VER_MINOR = 0
20-
VER_MICRO = 0
21-
22-
VERSION = '.'.join([str(VER_MAJOR),str(VER_MINOR),str(VER_MICRO)])
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
# Unless required by applicable law or agreed to in writing,
10+
# software distributed under the License is distributed on an
11+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
12+
# KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations
14+
# under the License.
15+
16+
17+
18+
VER_MAJOR = 1
19+
VER_MINOR = 0
20+
VER_MICRO = 0
21+
22+
VERSION = '.'.join([str(VER_MAJOR),str(VER_MINOR),str(VER_MICRO)])

drivers/python/age/__init__.py

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,40 @@
1-
# Licensed to the Apache Software Foundation (ASF) under one
2-
# or more contributor license agreements. See the NOTICE file
3-
# distributed with this work for additional information
4-
# regarding copyright ownership. The ASF licenses this file
5-
# to you under the Apache License, Version 2.0 (the
6-
# "License"); you may not use this file except in compliance
7-
# with the License. You may obtain a copy of the License at
8-
# http://www.apache.org/licenses/LICENSE-2.0
9-
# Unless required by applicable law or agreed to in writing,
10-
# software distributed under the License is distributed on an
11-
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
12-
# KIND, either express or implied. See the License for the
13-
# specific language governing permissions and limitations
14-
# under the License.
15-
16-
import psycopg.conninfo as conninfo
17-
from . import age
18-
from .age import *
19-
from .models import *
20-
from .builder import ResultHandler, DummyResultHandler, parseAgeValue, newResultHandler
21-
from . import VERSION
22-
23-
def version():
24-
return VERSION.VERSION
25-
26-
27-
def connect(dsn=None, graph=None, connection_factory=None, cursor_factory=ClientCursor, load_from_plugins=False,
28-
**kwargs):
29-
30-
dsn = conninfo.make_conninfo('' if dsn is None else dsn, **kwargs)
31-
32-
ag = Age()
33-
ag.connect(dsn=dsn, graph=graph, connection_factory=connection_factory, cursor_factory=cursor_factory,
34-
load_from_plugins=load_from_plugins, **kwargs)
35-
return ag
36-
37-
# Dummy ResultHandler
38-
rawPrinter = DummyResultHandler()
39-
40-
__name__="age"
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
# Unless required by applicable law or agreed to in writing,
10+
# software distributed under the License is distributed on an
11+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
12+
# KIND, either express or implied. See the License for the
13+
# specific language governing permissions and limitations
14+
# under the License.
15+
16+
import psycopg.conninfo as conninfo
17+
from . import age
18+
from .age import *
19+
from .models import *
20+
from .builder import ResultHandler, DummyResultHandler, parseAgeValue, newResultHandler
21+
from . import VERSION
22+
23+
def version():
24+
return VERSION.VERSION
25+
26+
27+
def connect(dsn=None, graph=None, connection_factory=None, cursor_factory=ClientCursor, load_from_plugins=False,
28+
**kwargs):
29+
30+
dsn = conninfo.make_conninfo('' if dsn is None else dsn, **kwargs)
31+
32+
ag = Age()
33+
ag.connect(dsn=dsn, graph=graph, connection_factory=connection_factory, cursor_factory=cursor_factory,
34+
load_from_plugins=load_from_plugins, **kwargs)
35+
return ag
36+
37+
# Dummy ResultHandler
38+
rawPrinter = DummyResultHandler()
39+
40+
__name__="age"

0 commit comments

Comments
 (0)