@eamonboyle/mssql-mcp 1.0.0 → 1.1.0
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.
- package/README.md +31 -26
- package/dist/__tests__/db.test.d.ts +2 -0
- package/dist/__tests__/db.test.d.ts.map +1 -0
- package/dist/__tests__/db.test.js +204 -0
- package/dist/__tests__/db.test.js.map +1 -0
- package/dist/__tests__/validation.test.d.ts +2 -0
- package/dist/__tests__/validation.test.d.ts.map +1 -0
- package/dist/__tests__/validation.test.js +111 -0
- package/dist/__tests__/validation.test.js.map +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +30 -7
- package/dist/db.js.map +1 -1
- package/dist/index.js +39 -22
- package/dist/index.js.map +1 -1
- package/dist/tools/CreateIndexTool.d.ts.map +1 -1
- package/dist/tools/CreateIndexTool.js +15 -9
- package/dist/tools/CreateIndexTool.js.map +1 -1
- package/dist/tools/CreateTableTool.d.ts.map +1 -1
- package/dist/tools/CreateTableTool.js +12 -7
- package/dist/tools/CreateTableTool.js.map +1 -1
- package/dist/tools/DescribeTableTool.d.ts.map +1 -1
- package/dist/tools/DescribeTableTool.js +5 -2
- package/dist/tools/DescribeTableTool.js.map +1 -1
- package/dist/tools/DropTableTool.d.ts.map +1 -1
- package/dist/tools/DropTableTool.js +3 -3
- package/dist/tools/DropTableTool.js.map +1 -1
- package/dist/tools/InsertDataTool.d.ts.map +1 -1
- package/dist/tools/InsertDataTool.js +8 -8
- package/dist/tools/InsertDataTool.js.map +1 -1
- package/dist/tools/ListTableTool.d.ts.map +1 -1
- package/dist/tools/ListTableTool.js +6 -4
- package/dist/tools/ListTableTool.js.map +1 -1
- package/dist/tools/ReadDataTool.d.ts +0 -8
- package/dist/tools/ReadDataTool.d.ts.map +1 -1
- package/dist/tools/ReadDataTool.js +15 -138
- package/dist/tools/ReadDataTool.js.map +1 -1
- package/dist/tools/UpdateDataTool.d.ts.map +1 -1
- package/dist/tools/UpdateDataTool.js +5 -5
- package/dist/tools/UpdateDataTool.js.map +1 -1
- package/dist/validation.d.ts +14 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +134 -0
- package/dist/validation.js.map +1 -0
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# MSSQL MCP Server
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.npmjs.com/package/@eamonboyle/mssql-mcp)
|
|
4
5
|
[](https://nodejs.org/)
|
|
5
6
|
|
|
6
|
-
[](cursor
|
|
7
|
-
[](
|
|
7
|
+
[](https://cursor.com/en/install-mcp?name=MSSQL&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBlYW1vbmJveWxlL21zc3FsLW1jcCJdLCJlbnYiOnsiU0VSVkVSX05BTUUiOiJsb2NhbGhvc3QiLCJEQVRBQkFTRV9OQU1FIjoiWW91ckRhdGFiYXNlIiwiREJfVVNFUiI6IiIsIkRCX1BBU1NXT1JEIjoiIiwiUkVBRE9OTFkiOiJmYWxzZSJ9fQ==)
|
|
8
|
+
[](https://intradeus.github.io/http-protocol-redirector?r=vscode%3A%2F%2Fmcp%2Finstall%3F%257B%2522name%2522%253A%2522mssql%2522%252C%2522type%2522%253A%2522stdio%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522%2540eamonboyle%252Fmssql-mcp%2522%255D%252C%2522env%2522%253A%257B%2522SERVER_NAME%2522%253A%2522localhost%2522%252C%2522DATABASE_NAME%2522%253A%2522YourDatabase%2522%252C%2522DB_USER%2522%253A%2522%2522%252C%2522DB_PASSWORD%2522%253A%2522%2522%252C%2522READONLY%2522%253A%2522false%2522%257D%257D)
|
|
8
9
|
|
|
9
10
|
> ⚠️ **EXPERIMENTAL USE ONLY** — This MCP Server is provided for educational and experimental purposes. It is NOT intended for production use. Use appropriate security measures and test thoroughly before any deployment.
|
|
10
11
|
|
|
@@ -70,19 +71,21 @@ npm run build
|
|
|
70
71
|
|
|
71
72
|
### Environment Variables
|
|
72
73
|
|
|
73
|
-
| Variable
|
|
74
|
-
|
|
75
|
-
| `SERVER_NAME`
|
|
76
|
-
| `DATABASE_NAME`
|
|
77
|
-
| `DB_USER`
|
|
78
|
-
| `DB_PASSWORD`
|
|
79
|
-
| `READONLY`
|
|
80
|
-
| `DATABASES`
|
|
81
|
-
| `CONNECTION_TIMEOUT`
|
|
82
|
-
| `TRUST_SERVER_CERTIFICATE` | No
|
|
74
|
+
| Variable | Required | Description |
|
|
75
|
+
| -------------------------- | -------- | ------------------------------------------------------------------------------------ |
|
|
76
|
+
| `SERVER_NAME` | Yes | SQL Server host (e.g., `localhost`, `my-server.database.windows.net`) |
|
|
77
|
+
| `DATABASE_NAME` | Yes\*\* | Default database name. Optional when `DATABASES` is set. |
|
|
78
|
+
| `DB_USER` | Yes\* | SQL Server username (for SQL authentication) |
|
|
79
|
+
| `DB_PASSWORD` | Yes\* | SQL Server password (for SQL authentication) |
|
|
80
|
+
| `READONLY` | No | `"true"` for read-only mode, `"false"` for full access (default: `"false"`) |
|
|
81
|
+
| `DATABASES` | No | Comma-separated allowlist for multi-database access (e.g., `ProdDB,StagingDB`) |
|
|
82
|
+
| `CONNECTION_TIMEOUT` | No | Timeout in seconds (default: `30`) |
|
|
83
|
+
| `TRUST_SERVER_CERTIFICATE` | No | `"true"` for self-signed certs (e.g., local dev) (default: `"false"`) |
|
|
83
84
|
|
|
84
85
|
\* Required for SQL authentication. For Windows/Integrated authentication, consult the [mssql](https://www.npmjs.com/package/mssql) package documentation.
|
|
85
86
|
|
|
87
|
+
\*\* Required for single-database setups. When `DATABASES` is provided, `DATABASE_NAME` becomes optional and is used as the default database if set.
|
|
88
|
+
|
|
86
89
|
### Option 1: Cursor / VS Code Setup
|
|
87
90
|
|
|
88
91
|
1. Create or edit `.vscode/mcp.json` in your workspace:
|
|
@@ -95,7 +98,8 @@ npm run build
|
|
|
95
98
|
"args": ["-y", "@eamonboyle/mssql-mcp"],
|
|
96
99
|
"env": {
|
|
97
100
|
"SERVER_NAME": "localhost",
|
|
98
|
-
"DATABASE_NAME": "
|
|
101
|
+
"DATABASE_NAME": "AppDB",
|
|
102
|
+
"DATABASES": "AppDB,ReportingDB",
|
|
99
103
|
"DB_USER": "your_username",
|
|
100
104
|
"DB_PASSWORD": "your_password",
|
|
101
105
|
"READONLY": "false"
|
|
@@ -120,7 +124,8 @@ npm run build
|
|
|
120
124
|
"args": ["-y", "@eamonboyle/mssql-mcp"],
|
|
121
125
|
"env": {
|
|
122
126
|
"SERVER_NAME": "localhost",
|
|
123
|
-
"DATABASE_NAME": "
|
|
127
|
+
"DATABASE_NAME": "AppDB",
|
|
128
|
+
"DATABASES": "AppDB,ReportingDB",
|
|
124
129
|
"DB_USER": "your_username",
|
|
125
130
|
"DB_PASSWORD": "your_password",
|
|
126
131
|
"READONLY": "false"
|
|
@@ -147,7 +152,7 @@ To allow queries across multiple databases:
|
|
|
147
152
|
}
|
|
148
153
|
```
|
|
149
154
|
|
|
150
|
-
All tools accept an optional `databaseName` parameter. When omitted, `DATABASE_NAME` is
|
|
155
|
+
`DATABASES` defines which databases the MCP can access. All tools accept an optional `databaseName` parameter. When omitted, the server uses `DATABASE_NAME` if it is included in `DATABASES`; otherwise it falls back to the first entry in `DATABASES`.
|
|
151
156
|
|
|
152
157
|
## Sample Configurations
|
|
153
158
|
|
|
@@ -168,16 +173,16 @@ Once configured, you can ask things like:
|
|
|
168
173
|
|
|
169
174
|
## Available Tools
|
|
170
175
|
|
|
171
|
-
| Tool
|
|
172
|
-
|
|
173
|
-
| `read_data`
|
|
174
|
-
| `list_table`
|
|
175
|
-
| `describe_table` | ✓
|
|
176
|
-
| `insert_data`
|
|
177
|
-
| `update_data`
|
|
178
|
-
| `create_table`
|
|
179
|
-
| `create_index`
|
|
180
|
-
| `drop_table`
|
|
176
|
+
| Tool | Read-only | Description |
|
|
177
|
+
| ---------------- | --------- | ---------------------------- |
|
|
178
|
+
| `read_data` | ✓ | Execute SELECT queries |
|
|
179
|
+
| `list_table` | ✓ | List tables in a database |
|
|
180
|
+
| `describe_table` | ✓ | Get table schema |
|
|
181
|
+
| `insert_data` | | Insert rows |
|
|
182
|
+
| `update_data` | | Update rows (requires WHERE) |
|
|
183
|
+
| `create_table` | | Create tables |
|
|
184
|
+
| `create_index` | | Create indexes |
|
|
185
|
+
| `drop_table` | | Drop tables |
|
|
181
186
|
|
|
182
187
|
## Security Notes
|
|
183
188
|
|
|
@@ -188,7 +193,7 @@ Once configured, you can ask things like:
|
|
|
188
193
|
|
|
189
194
|
## Contributing
|
|
190
195
|
|
|
191
|
-
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
196
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. By participating, you agree to uphold our [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
192
197
|
|
|
193
198
|
## License
|
|
194
199
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/db.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
const mssqlMockState = vi.hoisted(() => ({
|
|
3
|
+
poolConfigs: [],
|
|
4
|
+
requestObject: { kind: "mock-request" },
|
|
5
|
+
connectCalls: vi.fn(),
|
|
6
|
+
closeCalls: vi.fn(),
|
|
7
|
+
requestCalls: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
// Mock mssql to avoid loading native deps and transitive issues in test env
|
|
10
|
+
vi.mock("mssql", () => {
|
|
11
|
+
class MockConnectionPool {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.connected = false;
|
|
15
|
+
mssqlMockState.poolConfigs.push(config);
|
|
16
|
+
}
|
|
17
|
+
async connect() {
|
|
18
|
+
this.connected = true;
|
|
19
|
+
mssqlMockState.connectCalls(this.config);
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
async close() {
|
|
23
|
+
this.connected = false;
|
|
24
|
+
mssqlMockState.closeCalls(this.config);
|
|
25
|
+
}
|
|
26
|
+
request() {
|
|
27
|
+
mssqlMockState.requestCalls(this.config);
|
|
28
|
+
return mssqlMockState.requestObject;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
default: {
|
|
33
|
+
ConnectionPool: MockConnectionPool,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
import { getAllowedDatabases, getSqlRequest, resolveDatabaseName } from "../db.js";
|
|
38
|
+
describe("getAllowedDatabases", () => {
|
|
39
|
+
const originalEnv = process.env;
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
process.env = { ...originalEnv };
|
|
42
|
+
mssqlMockState.poolConfigs.length = 0;
|
|
43
|
+
mssqlMockState.connectCalls.mockClear();
|
|
44
|
+
mssqlMockState.closeCalls.mockClear();
|
|
45
|
+
mssqlMockState.requestCalls.mockClear();
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
process.env = originalEnv;
|
|
49
|
+
});
|
|
50
|
+
it("returns DATABASE_NAME when DATABASES is not set", () => {
|
|
51
|
+
delete process.env.DATABASES;
|
|
52
|
+
process.env.DATABASE_NAME = "MyDb";
|
|
53
|
+
expect(getAllowedDatabases()).toEqual(["MyDb"]);
|
|
54
|
+
});
|
|
55
|
+
it("returns empty array when neither DATABASES nor DATABASE_NAME is set", () => {
|
|
56
|
+
delete process.env.DATABASES;
|
|
57
|
+
delete process.env.DATABASE_NAME;
|
|
58
|
+
expect(getAllowedDatabases()).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
it("returns empty array when DATABASE_NAME is empty string", () => {
|
|
61
|
+
delete process.env.DATABASES;
|
|
62
|
+
process.env.DATABASE_NAME = "";
|
|
63
|
+
expect(getAllowedDatabases()).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
it("returns DATABASES split by comma when set", () => {
|
|
66
|
+
process.env.DATABASES = "ProdDB,StagingDB,AnalyticsDB";
|
|
67
|
+
expect(getAllowedDatabases()).toEqual([
|
|
68
|
+
"ProdDB",
|
|
69
|
+
"StagingDB",
|
|
70
|
+
"AnalyticsDB",
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
it("trims whitespace from DATABASES entries", () => {
|
|
74
|
+
process.env.DATABASES = " ProdDB , StagingDB , AnalyticsDB ";
|
|
75
|
+
expect(getAllowedDatabases()).toEqual([
|
|
76
|
+
"ProdDB",
|
|
77
|
+
"StagingDB",
|
|
78
|
+
"AnalyticsDB",
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
it("filters out empty entries from DATABASES", () => {
|
|
82
|
+
process.env.DATABASES = "ProdDB,,StagingDB,";
|
|
83
|
+
expect(getAllowedDatabases()).toEqual(["ProdDB", "StagingDB"]);
|
|
84
|
+
});
|
|
85
|
+
it("prefers DATABASES over DATABASE_NAME when both set", () => {
|
|
86
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
87
|
+
process.env.DATABASE_NAME = "OtherDb";
|
|
88
|
+
expect(getAllowedDatabases()).toEqual(["ProdDB", "StagingDB"]);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe("resolveDatabaseName", () => {
|
|
92
|
+
const originalEnv = process.env;
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
process.env = { ...originalEnv };
|
|
95
|
+
mssqlMockState.poolConfigs.length = 0;
|
|
96
|
+
mssqlMockState.connectCalls.mockClear();
|
|
97
|
+
mssqlMockState.closeCalls.mockClear();
|
|
98
|
+
mssqlMockState.requestCalls.mockClear();
|
|
99
|
+
});
|
|
100
|
+
afterEach(() => {
|
|
101
|
+
process.env = originalEnv;
|
|
102
|
+
});
|
|
103
|
+
it("returns param when valid and in allowed list", () => {
|
|
104
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
105
|
+
expect(resolveDatabaseName("ProdDB")).toBe("ProdDB");
|
|
106
|
+
expect(resolveDatabaseName("StagingDB")).toBe("StagingDB");
|
|
107
|
+
});
|
|
108
|
+
it("returns DATABASE_NAME when param omitted and DATABASES not set", () => {
|
|
109
|
+
process.env.DATABASE_NAME = "DefaultDb";
|
|
110
|
+
delete process.env.DATABASES;
|
|
111
|
+
expect(resolveDatabaseName()).toBe("DefaultDb");
|
|
112
|
+
});
|
|
113
|
+
it("returns DATABASE_NAME when it is in DATABASES", () => {
|
|
114
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
115
|
+
process.env.DATABASE_NAME = "StagingDB";
|
|
116
|
+
expect(resolveDatabaseName()).toBe("StagingDB");
|
|
117
|
+
});
|
|
118
|
+
it("returns the first DATABASES entry when DATABASE_NAME is omitted", () => {
|
|
119
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
120
|
+
delete process.env.DATABASE_NAME;
|
|
121
|
+
expect(resolveDatabaseName()).toBe("ProdDB");
|
|
122
|
+
});
|
|
123
|
+
it("returns param when param provided and in allowed list", () => {
|
|
124
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
125
|
+
process.env.DATABASE_NAME = "ProdDB";
|
|
126
|
+
expect(resolveDatabaseName("StagingDB")).toBe("StagingDB");
|
|
127
|
+
});
|
|
128
|
+
it("falls back to the first DATABASES entry when DATABASE_NAME is not included in DATABASES", () => {
|
|
129
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
130
|
+
process.env.DATABASE_NAME = "OtherDb";
|
|
131
|
+
expect(resolveDatabaseName()).toBe("ProdDB");
|
|
132
|
+
});
|
|
133
|
+
it("returns null when param not in allowed list", () => {
|
|
134
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
135
|
+
expect(resolveDatabaseName("OtherDb")).toBeNull();
|
|
136
|
+
});
|
|
137
|
+
it("returns null when param is empty and DATABASE_NAME not set", () => {
|
|
138
|
+
delete process.env.DATABASE_NAME;
|
|
139
|
+
delete process.env.DATABASES;
|
|
140
|
+
expect(resolveDatabaseName()).toBeNull();
|
|
141
|
+
expect(resolveDatabaseName("")).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
it("returns resolved name when no DATABASES set (only DATABASE_NAME allowed)", () => {
|
|
144
|
+
delete process.env.DATABASES;
|
|
145
|
+
process.env.DATABASE_NAME = "MyDb";
|
|
146
|
+
expect(resolveDatabaseName()).toBe("MyDb");
|
|
147
|
+
expect(resolveDatabaseName("MyDb")).toBe("MyDb");
|
|
148
|
+
expect(resolveDatabaseName("AnyDb")).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
it("trims whitespace from param", () => {
|
|
151
|
+
process.env.DATABASES = "ProdDB";
|
|
152
|
+
expect(resolveDatabaseName(" ProdDB ")).toBe("ProdDB");
|
|
153
|
+
});
|
|
154
|
+
it("returns null when neither DATABASE_NAME nor DATABASES is configured", () => {
|
|
155
|
+
delete process.env.DATABASE_NAME;
|
|
156
|
+
delete process.env.DATABASES;
|
|
157
|
+
expect(resolveDatabaseName()).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe("getSqlRequest", () => {
|
|
161
|
+
const originalEnv = process.env;
|
|
162
|
+
beforeEach(() => {
|
|
163
|
+
process.env = { ...originalEnv };
|
|
164
|
+
mssqlMockState.poolConfigs.length = 0;
|
|
165
|
+
mssqlMockState.connectCalls.mockClear();
|
|
166
|
+
mssqlMockState.closeCalls.mockClear();
|
|
167
|
+
mssqlMockState.requestCalls.mockClear();
|
|
168
|
+
});
|
|
169
|
+
afterEach(() => {
|
|
170
|
+
process.env = originalEnv;
|
|
171
|
+
});
|
|
172
|
+
it("returns a helpful error when no database access is configured", async () => {
|
|
173
|
+
delete process.env.DATABASE_NAME;
|
|
174
|
+
delete process.env.DATABASES;
|
|
175
|
+
const result = await getSqlRequest();
|
|
176
|
+
expect(result.error).toBe("Invalid or disallowed database. Set DATABASE_NAME or DATABASES to configure database access. Use the databaseName parameter to target a specific configured database.");
|
|
177
|
+
});
|
|
178
|
+
it("returns allowed databases in the error when a disallowed database is requested", async () => {
|
|
179
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
180
|
+
const result = await getSqlRequest("OtherDb");
|
|
181
|
+
expect(result.error).toBe("Invalid or disallowed database. Allowed: ProdDB, StagingDB. Use the databaseName parameter to target a specific configured database.");
|
|
182
|
+
});
|
|
183
|
+
it("returns a request using the resolved default database", async () => {
|
|
184
|
+
process.env.DATABASES = "ProdDBDefault,StagingDB";
|
|
185
|
+
delete process.env.DATABASE_NAME;
|
|
186
|
+
const result = await getSqlRequest();
|
|
187
|
+
expect(result.error).toBeUndefined();
|
|
188
|
+
expect(result.request).toBe(mssqlMockState.requestObject);
|
|
189
|
+
expect(mssqlMockState.poolConfigs).toContainEqual(expect.objectContaining({ database: "ProdDBDefault" }));
|
|
190
|
+
expect(mssqlMockState.connectCalls).toHaveBeenCalledWith(expect.objectContaining({ database: "ProdDBDefault" }));
|
|
191
|
+
expect(mssqlMockState.requestCalls).toHaveBeenCalledWith(expect.objectContaining({ database: "ProdDBDefault" }));
|
|
192
|
+
});
|
|
193
|
+
it("falls back to the first allowed database when DATABASE_NAME is disallowed", async () => {
|
|
194
|
+
process.env.DATABASES = "ProdDB,StagingDB";
|
|
195
|
+
process.env.DATABASE_NAME = "OtherDb";
|
|
196
|
+
const result = await getSqlRequest();
|
|
197
|
+
expect(result.error).toBeUndefined();
|
|
198
|
+
expect(result.request).toBe(mssqlMockState.requestObject);
|
|
199
|
+
expect(mssqlMockState.poolConfigs).toContainEqual(expect.objectContaining({ database: "ProdDB" }));
|
|
200
|
+
expect(mssqlMockState.connectCalls).toHaveBeenCalledWith(expect.objectContaining({ database: "ProdDB" }));
|
|
201
|
+
expect(mssqlMockState.requestCalls).toHaveBeenCalledWith(expect.objectContaining({ database: "ProdDB" }));
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
//# sourceMappingURL=db.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.test.js","sourceRoot":"","sources":["../../src/__tests__/db.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/validation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { validateReadQuery } from "../validation.js";
|
|
3
|
+
describe("validateReadQuery", () => {
|
|
4
|
+
it("accepts simple SELECT query", () => {
|
|
5
|
+
expect(validateReadQuery("SELECT * FROM movies")).toEqual({
|
|
6
|
+
isValid: true,
|
|
7
|
+
});
|
|
8
|
+
expect(validateReadQuery("SELECT id, name FROM users WHERE id = 1")).toEqual({
|
|
9
|
+
isValid: true,
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
it("rejects empty or non-string input", () => {
|
|
13
|
+
expect(validateReadQuery("")).toEqual({
|
|
14
|
+
isValid: false,
|
|
15
|
+
error: "Query must be a non-empty string",
|
|
16
|
+
});
|
|
17
|
+
expect(validateReadQuery(" ")).toEqual({
|
|
18
|
+
isValid: false,
|
|
19
|
+
error: "Query cannot be empty after removing comments",
|
|
20
|
+
});
|
|
21
|
+
expect(validateReadQuery(null)).toEqual({
|
|
22
|
+
isValid: false,
|
|
23
|
+
error: "Query must be a non-empty string",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it("rejects query that does not start with SELECT", () => {
|
|
27
|
+
expect(validateReadQuery("DELETE FROM users")).toEqual({
|
|
28
|
+
isValid: false,
|
|
29
|
+
error: "Query must start with SELECT for security reasons",
|
|
30
|
+
});
|
|
31
|
+
expect(validateReadQuery(" UPDATE users SET x = 1")).toEqual({
|
|
32
|
+
isValid: false,
|
|
33
|
+
error: "Query must start with SELECT for security reasons",
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it("rejects dangerous keywords", () => {
|
|
37
|
+
expect(validateReadQuery("SELECT * FROM users; DELETE FROM logs")).toEqual({
|
|
38
|
+
isValid: false,
|
|
39
|
+
error: expect.stringContaining("Dangerous keyword"),
|
|
40
|
+
});
|
|
41
|
+
expect(validateReadQuery("SELECT * INTO backup FROM users")).toEqual({
|
|
42
|
+
isValid: false,
|
|
43
|
+
error: expect.stringContaining("Dangerous keyword"),
|
|
44
|
+
});
|
|
45
|
+
expect(validateReadQuery("SELECT * FROM users; DROP TABLE users")).toEqual({
|
|
46
|
+
isValid: false,
|
|
47
|
+
error: expect.stringContaining("Dangerous keyword"),
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
it("rejects SELECT INTO pattern", () => {
|
|
51
|
+
const result = validateReadQuery("SELECT * INTO newtable FROM users");
|
|
52
|
+
expect(result.isValid).toBe(false);
|
|
53
|
+
expect(result.error).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
it("rejects multiple statements", () => {
|
|
56
|
+
const result = validateReadQuery("SELECT 1; SELECT 2");
|
|
57
|
+
expect(result.isValid).toBe(false);
|
|
58
|
+
expect(result.error).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
it("rejects character conversion functions", () => {
|
|
61
|
+
expect(validateReadQuery("SELECT CHAR(65) FROM users")).toEqual({
|
|
62
|
+
isValid: false,
|
|
63
|
+
error: "Character conversion functions are not allowed as they may be used for obfuscation.",
|
|
64
|
+
});
|
|
65
|
+
expect(validateReadQuery("SELECT NCHAR(65) FROM users")).toEqual({
|
|
66
|
+
isValid: false,
|
|
67
|
+
error: "Character conversion functions are not allowed as they may be used for obfuscation.",
|
|
68
|
+
});
|
|
69
|
+
expect(validateReadQuery("SELECT ASCII('A') FROM users")).toEqual({
|
|
70
|
+
isValid: false,
|
|
71
|
+
error: "Character conversion functions are not allowed as they may be used for obfuscation.",
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it("rejects query exceeding length limit", () => {
|
|
75
|
+
const longQuery = "SELECT * FROM users WHERE " + "x = 1 AND ".repeat(2000);
|
|
76
|
+
expect(longQuery.length).toBeGreaterThan(10000);
|
|
77
|
+
expect(validateReadQuery(longQuery)).toEqual({
|
|
78
|
+
isValid: false,
|
|
79
|
+
error: "Query is too long. Maximum allowed length is 10,000 characters.",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
it("rejects stored procedure patterns", () => {
|
|
83
|
+
expect(validateReadQuery("SELECT * FROM sp_help")).toEqual({
|
|
84
|
+
isValid: false,
|
|
85
|
+
error: "Potentially malicious SQL pattern detected. Only simple SELECT queries are allowed.",
|
|
86
|
+
});
|
|
87
|
+
expect(validateReadQuery("SELECT * FROM xp_cmdshell")).toEqual({
|
|
88
|
+
isValid: false,
|
|
89
|
+
error: "Potentially malicious SQL pattern detected. Only simple SELECT queries are allowed.",
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
it("rejects WAITFOR delay/time patterns", () => {
|
|
93
|
+
const result = validateReadQuery("SELECT 1; WAITFOR DELAY '0:0:5'");
|
|
94
|
+
expect(result.isValid).toBe(false);
|
|
95
|
+
expect(result.error).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
it("rejects query that is only comments", () => {
|
|
98
|
+
expect(validateReadQuery("-- comment only")).toEqual({
|
|
99
|
+
isValid: false,
|
|
100
|
+
error: "Query cannot be empty after removing comments",
|
|
101
|
+
});
|
|
102
|
+
expect(validateReadQuery("/* block comment */")).toEqual({
|
|
103
|
+
isValid: false,
|
|
104
|
+
error: "Query cannot be empty after removing comments",
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it("accepts SELECT with WHERE and JOIN", () => {
|
|
108
|
+
expect(validateReadQuery("SELECT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id WHERE u.active = 1")).toEqual({ isValid: true });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
//# sourceMappingURL=validation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.test.js","sourceRoot":"","sources":["../../src/__tests__/validation.test.ts"],"names":[],"mappings":"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"}
|
package/dist/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,OAAO,CAAC;AA+BxB;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAQ9C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQxE;AAwBD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAoB7B;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAiBnD"}
|
package/dist/db.js
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
import sql from "mssql";
|
|
2
2
|
// Connection pools keyed by database name
|
|
3
3
|
const sqlPools = new Map();
|
|
4
|
+
function parseDatabaseList(value) {
|
|
5
|
+
if (!value || !value.trim()) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
return value
|
|
9
|
+
.split(",")
|
|
10
|
+
.map((db) => db.trim())
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
function getDefaultDatabaseName() {
|
|
14
|
+
const allowedDatabases = parseDatabaseList(process.env.DATABASES);
|
|
15
|
+
const explicitDefault = process.env.DATABASE_NAME?.trim();
|
|
16
|
+
if (allowedDatabases.length === 0) {
|
|
17
|
+
return explicitDefault ?? null;
|
|
18
|
+
}
|
|
19
|
+
if (explicitDefault && allowedDatabases.includes(explicitDefault)) {
|
|
20
|
+
return explicitDefault;
|
|
21
|
+
}
|
|
22
|
+
return allowedDatabases[0] ?? null;
|
|
23
|
+
}
|
|
4
24
|
/**
|
|
5
25
|
* Returns the list of allowed database names.
|
|
6
26
|
* If DATABASES is set, returns those; otherwise returns only DATABASE_NAME.
|
|
7
27
|
*/
|
|
8
28
|
export function getAllowedDatabases() {
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
29
|
+
const allowedDatabases = parseDatabaseList(process.env.DATABASES);
|
|
30
|
+
if (allowedDatabases.length > 0) {
|
|
31
|
+
return allowedDatabases;
|
|
12
32
|
}
|
|
13
|
-
const defaultDb = process.env.DATABASE_NAME;
|
|
33
|
+
const defaultDb = process.env.DATABASE_NAME?.trim();
|
|
14
34
|
return defaultDb ? [defaultDb] : [];
|
|
15
35
|
}
|
|
16
36
|
/**
|
|
@@ -18,12 +38,12 @@ export function getAllowedDatabases() {
|
|
|
18
38
|
* Returns null if invalid.
|
|
19
39
|
*/
|
|
20
40
|
export function resolveDatabaseName(databaseName) {
|
|
21
|
-
const resolved = databaseName?.trim() ||
|
|
41
|
+
const resolved = databaseName?.trim() || getDefaultDatabaseName();
|
|
22
42
|
if (!resolved)
|
|
23
43
|
return null;
|
|
24
44
|
const allowed = getAllowedDatabases();
|
|
25
45
|
if (allowed.length === 0)
|
|
26
|
-
return
|
|
46
|
+
return null;
|
|
27
47
|
if (allowed.includes(resolved))
|
|
28
48
|
return resolved;
|
|
29
49
|
return null;
|
|
@@ -79,9 +99,12 @@ export async function getSqlRequest(databaseName) {
|
|
|
79
99
|
const resolved = resolveDatabaseName(databaseName);
|
|
80
100
|
if (!resolved) {
|
|
81
101
|
const allowed = getAllowedDatabases();
|
|
102
|
+
const configurationHint = allowed.length > 0
|
|
103
|
+
? `Allowed: ${allowed.join(", ")}.`
|
|
104
|
+
: "Set DATABASE_NAME or DATABASES to configure database access.";
|
|
82
105
|
return {
|
|
83
106
|
request: null,
|
|
84
|
-
error: `Invalid or disallowed database.
|
|
107
|
+
error: `Invalid or disallowed database. ${configurationHint} Use the databaseName parameter to target a specific configured database.`,
|
|
85
108
|
};
|
|
86
109
|
}
|
|
87
110
|
const pool = await ensureSqlConnection(resolved);
|
package/dist/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,OAAO,CAAC;AAExB,0CAA0C;AAC1C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAC;AAEvD
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,OAAO,CAAC;AAExB,0CAA0C;AAC1C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAC;AAEvD,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE;QAC3B,OAAO,EAAE,CAAC;KACX;IAED,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;SACtB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,sBAAsB;IAC7B,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAClE,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;IAE1D,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE;QACjC,OAAO,eAAe,IAAI,IAAI,CAAC;KAChC;IAED,IAAI,eAAe,IAAI,gBAAgB,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE;QACjE,OAAO,eAAe,CAAC;KACxB;IAED,OAAO,gBAAgB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAClE,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE;QAC/B,OAAO,gBAAgB,CAAC;KACzB;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;IACpD,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,YAAqB;IACvD,MAAM,QAAQ,GAAG,YAAY,EAAE,IAAI,EAAE,IAAI,sBAAsB,EAAE,CAAC;IAClE,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;IACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,0BAA0B,CAAC,YAAoB;IACtD,MAAM,sBAAsB,GAC1B,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,WAAW,EAAE,KAAK,MAAM,CAAC;IACjE,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB;QACtD,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,EAAE,CAAC;QAC9C,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,WAAW;QAC9C,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO;QACzB,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW;QACjC,OAAO,EAAE;YACP,OAAO,EAAE,KAAK;YACd,sBAAsB,EAAE,sBAAsB,IAAI,IAAI;YACtD,gBAAgB,EAAE,IAAI;YACtB,MAAM,EAAE,KAAK;SACd;QACD,iBAAiB,EAAE,iBAAiB,GAAG,IAAI;KAC9B,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,YAAoB;IAEpB,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,QAAQ,IAAI,QAAQ,CAAC,SAAS,EAAE;QAClC,OAAO,QAAQ,CAAC;KACjB;IAED,IAAI,QAAQ,EAAE;QACZ,IAAI;YACF,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;SACxB;QAAC,MAAM;YACN,sBAAsB;SACvB;QACD,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;KAC/B;IAED,MAAM,MAAM,GAAG,0BAA0B,CAAC,YAAY,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACrB,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IACjC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,YAAqB;IAErB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACnD,IAAI,CAAC,QAAQ,EAAE;QACb,MAAM,OAAO,GAAG,mBAAmB,EAAE,CAAC;QACtC,MAAM,iBAAiB,GACrB,OAAO,CAAC,MAAM,GAAG,CAAC;YAChB,CAAC,CAAC,YAAY,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACnC,CAAC,CAAC,8DAA8D,CAAC;QAErE,OAAO;YACL,OAAO,EAAE,IAAW;YACpB,KAAK,EAAE,mCAAmC,iBAAiB,2EAA2E;SACvI,CAAC;KACH;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;AACrC,CAAC"}
|