sql-data-guard 0.1.8__tar.gz → 0.1.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sql_data_guard-0.1.8/src/sql_data_guard.egg-info → sql_data_guard-0.1.9}/PKG-INFO +1 -1
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/pyproject.toml +1 -1
- sql_data_guard-0.1.9/src/sql_data_guard/mcpwrapper/mcp_wrapper.py +129 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9/src/sql_data_guard.egg-info}/PKG-INFO +1 -1
- sql_data_guard-0.1.8/src/sql_data_guard/mcpwrapper/mcp_wrapper.py +0 -86
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/LICENSE.md +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/README.md +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/setup.cfg +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/__init__.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/rest/__init__.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/rest/sql_data_guard_rest.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/restriction_validation.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/restriction_verification.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/sql_data_guard.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/verification_context.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/verification_utils.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard.egg-info/SOURCES.txt +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard.egg-info/dependency_links.txt +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard.egg-info/requires.txt +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard.egg-info/top_level.txt +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_duckdb_unit.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_rest_api_unit.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_sql_guard_curr_unit.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_sql_guard_joins_unit.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_sql_guard_llm.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_sql_guard_unit.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_sql_guard_updates_unit.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_sql_guard_validation_unit.py +0 -0
- {sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/test/test_utils.py +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Optional, Dict, List
|
|
6
|
+
|
|
7
|
+
import docker
|
|
8
|
+
|
|
9
|
+
from sql_data_guard import verify_sql
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_config() -> dict:
|
|
13
|
+
return json.load(open("/conf/config.json"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_volumes() -> List[str]:
|
|
17
|
+
volumes = config["mcp-server"].get("volumes", [])
|
|
18
|
+
if "PWD" in os.environ:
|
|
19
|
+
volumes = [v.replace("$PWD", os.environ["PWD"]) for v in volumes]
|
|
20
|
+
return volumes
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def start_inner_container():
|
|
24
|
+
client = docker.from_env()
|
|
25
|
+
container = client.containers.run(
|
|
26
|
+
config["mcp-server"]["image"],
|
|
27
|
+
(
|
|
28
|
+
" ".join(config["mcp-server"]["args"])
|
|
29
|
+
if "args" in config["mcp-server"]
|
|
30
|
+
else None
|
|
31
|
+
),
|
|
32
|
+
volumes=_get_volumes(),
|
|
33
|
+
network_mode=config["mcp-server"].get("network-mode"),
|
|
34
|
+
stdin_open=True,
|
|
35
|
+
auto_remove=True,
|
|
36
|
+
detach=True,
|
|
37
|
+
stdout=True,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def stream_output_inject_response():
|
|
41
|
+
for line in container.logs(stream=True):
|
|
42
|
+
line_json = json.loads(line)
|
|
43
|
+
request_id = line_json["id"]
|
|
44
|
+
if request_id in errors:
|
|
45
|
+
if "result" in line_json and "content" in line_json["result"]:
|
|
46
|
+
line_json["result"]["content"].insert(
|
|
47
|
+
0,
|
|
48
|
+
{
|
|
49
|
+
"type": "text",
|
|
50
|
+
"text": f"[{errors[request_id]}]",
|
|
51
|
+
"isError": True,
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
del errors[request_id]
|
|
55
|
+
sys.stdout.write(json.dumps(line_json) + "\n")
|
|
56
|
+
sys.stdout.flush()
|
|
57
|
+
|
|
58
|
+
def stream_output():
|
|
59
|
+
for line in container.logs(stream=True):
|
|
60
|
+
sys.stdout.write(line.decode("utf-8"))
|
|
61
|
+
sys.stdout.flush()
|
|
62
|
+
|
|
63
|
+
threading.Thread(
|
|
64
|
+
target=stream_output_inject_response if inject_response else stream_output,
|
|
65
|
+
daemon=True,
|
|
66
|
+
).start()
|
|
67
|
+
return container
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main():
|
|
71
|
+
container = start_inner_container()
|
|
72
|
+
try:
|
|
73
|
+
socket = container.attach_socket(params={"stdin": True, "stream": True})
|
|
74
|
+
# noinspection PyProtectedMember
|
|
75
|
+
socket._sock.setblocking(True)
|
|
76
|
+
for line in sys.stdin:
|
|
77
|
+
line = input_line(line)
|
|
78
|
+
# noinspection PyProtectedMember
|
|
79
|
+
socket._sock.sendall(line.encode("utf-8"))
|
|
80
|
+
except (KeyboardInterrupt, EOFError):
|
|
81
|
+
pass
|
|
82
|
+
finally:
|
|
83
|
+
container.stop()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_sql(json_line: dict) -> Optional[str]:
|
|
87
|
+
if json_line["method"] == "tools/call":
|
|
88
|
+
for tool in config["mcp-tools"]:
|
|
89
|
+
if tool["tool-name"] == json_line["params"]["name"]:
|
|
90
|
+
return json_line["params"]["arguments"][tool["arg-name"]]
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def input_line(line: str) -> str:
|
|
95
|
+
json_line = json.loads(line.encode("utf-8"))
|
|
96
|
+
sql = get_sql(json_line)
|
|
97
|
+
if sql:
|
|
98
|
+
result = verify_sql(
|
|
99
|
+
sql,
|
|
100
|
+
config["sql-data-guard"],
|
|
101
|
+
config["sql-data-guard"]["dialect"],
|
|
102
|
+
)
|
|
103
|
+
if not result["allowed"]:
|
|
104
|
+
sys.stderr.write(
|
|
105
|
+
f"ID: {json_line['id']} Blocked SQL: {sql}\nErrors: {list(result['errors'])}\n"
|
|
106
|
+
)
|
|
107
|
+
if result["fixed"]:
|
|
108
|
+
sys.stderr.write(f"Fixed SQL: {result['fixed']}\n")
|
|
109
|
+
updated_sql = result["fixed"]
|
|
110
|
+
else:
|
|
111
|
+
updated_sql = "SELECT 'Blocked by SQL Data Guard' AS message"
|
|
112
|
+
if not inject_response:
|
|
113
|
+
for error in result["errors"]:
|
|
114
|
+
updated_sql += f"\nUNION ALL SELECT '{error}' AS message"
|
|
115
|
+
if inject_response:
|
|
116
|
+
result["errors"] = list(result["errors"])
|
|
117
|
+
if result["fixed"]:
|
|
118
|
+
result["fixed"] = result["fixed"].replace("'", "''")
|
|
119
|
+
errors[json_line["id"]] = result
|
|
120
|
+
json_line["params"]["arguments"]["query"] = updated_sql
|
|
121
|
+
line = json.dumps(json_line) + "\n"
|
|
122
|
+
return line
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
config = load_config()
|
|
127
|
+
inject_response = config["sql-data-guard"]["inject-response"]
|
|
128
|
+
errors: Dict[int, dict] = {}
|
|
129
|
+
main()
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
import sys
|
|
4
|
-
import threading
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
import docker
|
|
8
|
-
from sql_data_guard import verify_sql
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def load_config() -> dict:
|
|
12
|
-
return json.load(open("/conf/config.json"))
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def start_inner_container():
|
|
16
|
-
client = docker.from_env()
|
|
17
|
-
container = client.containers.run(
|
|
18
|
-
config["mcp-server"]["image"],
|
|
19
|
-
" ".join(config["mcp-server"]["args"]),
|
|
20
|
-
volumes=[
|
|
21
|
-
v.replace("$PWD", os.environ["PWD"])
|
|
22
|
-
for v in config["mcp-server"]["volumes"]
|
|
23
|
-
],
|
|
24
|
-
stdin_open=True,
|
|
25
|
-
auto_remove=True,
|
|
26
|
-
detach=True,
|
|
27
|
-
stdout=True,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
def stream_output():
|
|
31
|
-
for line in container.logs(stream=True):
|
|
32
|
-
sys.stdout.write(line.decode("utf-8"))
|
|
33
|
-
sys.stdout.flush()
|
|
34
|
-
|
|
35
|
-
threading.Thread(target=stream_output, daemon=True).start()
|
|
36
|
-
return container
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def main():
|
|
40
|
-
container = start_inner_container()
|
|
41
|
-
|
|
42
|
-
try:
|
|
43
|
-
socket = container.attach_socket(params={"stdin": True, "stream": True})
|
|
44
|
-
# noinspection PyProtectedMember
|
|
45
|
-
socket._sock.setblocking(True)
|
|
46
|
-
for line in sys.stdin:
|
|
47
|
-
line = input_line(line)
|
|
48
|
-
# noinspection PyProtectedMember
|
|
49
|
-
socket._sock.sendall(line.encode("utf-8"))
|
|
50
|
-
except (KeyboardInterrupt, EOFError):
|
|
51
|
-
pass
|
|
52
|
-
finally:
|
|
53
|
-
container.stop()
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def get_sql(json_line: dict) -> Optional[str]:
|
|
57
|
-
sys.stderr.write(f"json_line: {json_line}\n")
|
|
58
|
-
if json_line["method"] == "tools/call":
|
|
59
|
-
for tool in config["mcp-tools"]:
|
|
60
|
-
if tool["tool-name"] == json_line["params"]["name"]:
|
|
61
|
-
return json_line["params"]["arguments"][tool["arg-name"]]
|
|
62
|
-
return None
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def input_line(line: str) -> str:
|
|
66
|
-
json_line = json.loads(line.encode("utf-8"))
|
|
67
|
-
sql = get_sql(json_line)
|
|
68
|
-
if sql:
|
|
69
|
-
result = verify_sql(
|
|
70
|
-
sql,
|
|
71
|
-
config["sql-data-guard"],
|
|
72
|
-
config["sql-data-guard"]["dialect"],
|
|
73
|
-
)
|
|
74
|
-
if not result["allowed"]:
|
|
75
|
-
sys.stderr.write(f"Blocked SQL: {sql}\nErrors: {list(result['errors'])}\n")
|
|
76
|
-
updated_sql = "SELECT 'Blocked by SQL Data Guard' AS message"
|
|
77
|
-
for error in result["errors"]:
|
|
78
|
-
updated_sql += f"\nUNION ALL SELECT '{error}' AS message"
|
|
79
|
-
json_line["params"]["arguments"]["query"] = updated_sql
|
|
80
|
-
line = json.dumps(json_line) + "\n"
|
|
81
|
-
return line
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if __name__ == "__main__":
|
|
85
|
-
config = load_config()
|
|
86
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/rest/sql_data_guard_rest.py
RENAMED
|
File without changes
|
|
File without changes
|
{sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard/restriction_verification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_data_guard-0.1.8 → sql_data_guard-0.1.9}/src/sql_data_guard.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|