From 0bd673826f14495065b2cc7e85ba702b178ba05f Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 27 Mar 2026 01:15:48 +0000 Subject: [PATCH 1/3] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/xlabtg/teleton-plugins/issues/64 --- .gitkeep | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitkeep b/.gitkeep index 80ab57e..404aa7e 100644 --- a/.gitkeep +++ b/.gitkeep @@ -1,2 +1,3 @@ # .gitkeep file auto-generated at 2026-03-19T10:37:13.073Z for PR creation at branch issue-19-f54b585823d1 for issue https://github.com/xlabtg/teleton-plugins/issues/19 -# Updated: 2026-03-27T01:04:44.761Z \ No newline at end of file +# Updated: 2026-03-27T01:04:44.761Z +# Updated: 2026-03-27T01:15:48.214Z \ No newline at end of file From 7960347fae238cc7fc933fbe8bde90a0541ad440 Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 27 Mar 2026 01:19:24 +0000 Subject: [PATCH 2/3] fix(ton-trading-bot): fix SQL injection in risk metrics and scheduled trades queries Replace string interpolation of `mode` and `status` enum values in SQL queries with parameterized `?` placeholders, consistent with all other queries in the file. Adds 4 regression tests that verify injection payloads never appear in query strings. Fixes #64 Co-Authored-By: Claude Sonnet 4.6 --- plugins/ton-trading-bot/index.js | 12 ++- plugins/ton-trading-bot/tests/index.test.js | 103 ++++++++++++++++++++ 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/plugins/ton-trading-bot/index.js b/plugins/ton-trading-bot/index.js index af7985c..2cd95b8 100644 --- a/plugins/ton-trading-bot/index.js +++ b/plugins/ton-trading-bot/index.js @@ -1582,14 +1582,16 @@ export const tools = (sdk) => [ try { const since = Date.now() - lookback_days * 24 * 60 * 60 * 1000; - const modeClause = mode === "all" ? "" : `AND mode = '${mode}'`; + const [modeClause, modeParams] = mode === "all" + ? ["", []] + : ["AND mode = ?", [mode]]; const trades = sdk.db .prepare( `SELECT pnl_percent FROM trade_journal WHERE status = 'closed' AND timestamp >= ? ${modeClause} ORDER BY timestamp ASC` ) - .all(since); + .all(since, ...modeParams); if (trades.length === 0) { return { @@ -2026,11 +2028,13 @@ export const tools = (sdk) => [ const status = params.status ?? "pending"; const limit = params.limit ?? 20; try { - const statusClause = status === "all" ? "" : `WHERE status = '${status}'`; + const [statusClause, statusParams] = status === "all" + ? ["", []] + : ["WHERE status = ?", [status]]; const scheduled = sdk.db .prepare(`SELECT * FROM scheduled_trades ${statusClause} ORDER BY execute_at ASC LIMIT ?`) - .all(limit); + .all(...statusParams, limit); const now = Date.now(); const annotated = scheduled.map((s) => ({ diff --git a/plugins/ton-trading-bot/tests/index.test.js b/plugins/ton-trading-bot/tests/index.test.js index 3b6c259..c6ca89f 100644 --- a/plugins/ton-trading-bot/tests/index.test.js +++ b/plugins/ton-trading-bot/tests/index.test.js @@ -1172,4 +1172,107 @@ describe("ton-trading-bot plugin", () => { assert.ok(result.data.scheduled_trades[1].is_due === false); }); }); + + // ── SQL injection tests ───────────────────────────────────────────────────── + describe("SQL injection prevention", () => { + it("ton_trading_calculate_risk_metrics: mode value is passed as parameter, not interpolated", async () => { + const capturedSqls = []; + const capturedParams = []; + const sdk = makeSdk({ + db: { + exec: () => {}, + prepare: (sql) => { + capturedSqls.push(sql); + return { + get: () => null, + all: (...params) => { capturedParams.push(params); return []; }, + run: () => ({ lastInsertRowid: 1 }), + }; + }, + }, + }); + const tool = mod.tools(sdk).find((t) => t.name === "ton_trading_calculate_risk_metrics"); + await tool.execute({ mode: "real", lookback_days: 7, confidence_level: 0.95 }, {}); + const riskSql = capturedSqls.find((s) => s.includes("trade_journal")); + assert.ok(riskSql, "trade_journal query should be captured"); + // SQL must not contain the literal mode value — it must use a placeholder + assert.ok(!riskSql.includes("'real'"), "mode value must not be string-interpolated into SQL"); + assert.ok(riskSql.includes("?"), "SQL must use parameterized placeholder"); + // The mode value must be passed as a bound parameter + const riskParams = capturedParams.find((p) => p.some((v) => v === "real")); + assert.ok(riskParams, "mode value 'real' must be passed as a bound parameter"); + }); + + it("ton_trading_calculate_risk_metrics: SQL injection payload is not interpolated", async () => { + const capturedSqls = []; + const sdk = makeSdk({ + db: { + exec: () => {}, + prepare: (sql) => { + capturedSqls.push(sql); + return { + get: () => null, + all: () => [], + run: () => ({ lastInsertRowid: 1 }), + }; + }, + }, + }); + const tool = mod.tools(sdk).find((t) => t.name === "ton_trading_calculate_risk_metrics"); + const injectionPayload = "real' OR '1'='1"; + await tool.execute({ mode: injectionPayload, lookback_days: 7, confidence_level: 0.95 }, {}); + const riskSql = capturedSqls.find((s) => s.includes("trade_journal")); + assert.ok(riskSql, "trade_journal query should be captured"); + assert.ok(!riskSql.includes(injectionPayload), "SQL injection payload must not appear in query string"); + }); + + it("ton_trading_get_scheduled_trades: status value is passed as parameter, not interpolated", async () => { + const capturedSqls = []; + const capturedParams = []; + const sdk = makeSdk({ + db: { + exec: () => {}, + prepare: (sql) => { + capturedSqls.push(sql); + return { + get: () => null, + all: (...params) => { capturedParams.push(params); return []; }, + run: () => ({ lastInsertRowid: 1 }), + }; + }, + }, + }); + const tool = mod.tools(sdk).find((t) => t.name === "ton_trading_get_scheduled_trades"); + await tool.execute({ status: "pending", limit: 10 }, {}); + const schedSql = capturedSqls.find((s) => s.includes("scheduled_trades")); + assert.ok(schedSql, "scheduled_trades query should be captured"); + assert.ok(!schedSql.includes("'pending'"), "status value must not be string-interpolated into SQL"); + assert.ok(schedSql.includes("?"), "SQL must use parameterized placeholder"); + const schedParams = capturedParams.find((p) => p.some((v) => v === "pending")); + assert.ok(schedParams, "status value 'pending' must be passed as a bound parameter"); + }); + + it("ton_trading_get_scheduled_trades: SQL injection payload is not interpolated", async () => { + const capturedSqls = []; + const sdk = makeSdk({ + db: { + exec: () => {}, + prepare: (sql) => { + capturedSqls.push(sql); + return { + get: () => null, + all: () => [], + run: () => ({ lastInsertRowid: 1 }), + }; + }, + }, + }); + const tool = mod.tools(sdk).find((t) => t.name === "ton_trading_get_scheduled_trades"); + const injectionPayload = "pending' OR '1'='1"; + await tool.execute({ status: injectionPayload, limit: 10 }, {}); + const schedSql = capturedSqls.find((s) => s.includes("scheduled_trades")); + assert.ok(schedSql, "scheduled_trades query should be captured"); + assert.ok(!schedSql.includes(injectionPayload), "SQL injection payload must not appear in query string"); + }); + }); }); From 8636d05ce4d2695a3c12909e40112e0752f6d834 Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 27 Mar 2026 01:20:04 +0000 Subject: [PATCH 3/3] Revert "Initial commit with task details" This reverts commit 0bd673826f14495065b2cc7e85ba702b178ba05f. --- .gitkeep | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitkeep b/.gitkeep index 404aa7e..80ab57e 100644 --- a/.gitkeep +++ b/.gitkeep @@ -1,3 +1,2 @@ # .gitkeep file auto-generated at 2026-03-19T10:37:13.073Z for PR creation at branch issue-19-f54b585823d1 for issue https://github.com/xlabtg/teleton-plugins/issues/19 -# Updated: 2026-03-27T01:04:44.761Z -# Updated: 2026-03-27T01:15:48.214Z \ No newline at end of file +# Updated: 2026-03-27T01:04:44.761Z \ No newline at end of file