Skip to content

Commit 6eea3b1

Browse files
ffMathygismya
andcommitted
feat(security): add injection prevention (#139)
* feat(security): add injection prevention * Update session.ts * Add tests for template tag * Add the prepared import to the test * fix: review changes * chore: rename `prepared` to `expression` * fix: conflict fixes * fix: compile errors --------- Co-authored-by: Lars Johansson <gismya@gmail.com>
1 parent 317ca08 commit 6eea3b1

File tree

2 files changed

+113
-3
lines changed

2 files changed

+113
-3
lines changed

source/session.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,3 +1012,21 @@ export class Session {
10121012
});
10131013
}
10141014
}
1015+
1016+
/**
1017+
* Tagged string template for preparing statements to prevent injection attacks.
1018+
*/
1019+
export function expression(
1020+
literals: TemplateStringsArray,
1021+
...placeholders: string[]
1022+
): string {
1023+
let result = "";
1024+
1025+
for (let i = 0; i < placeholders.length; i++) {
1026+
result += literals[i];
1027+
result += placeholders[i].replace(/(['"])/g, "\\$1");
1028+
}
1029+
1030+
result += literals[literals.length - 1];
1031+
return result;
1032+
}

test/session.test.ts

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
ServerValidationError,
1010
ServerError,
1111
} from "../source/error";
12-
import { Session } from "../source/session";
12+
import { Session, expression } from "../source/session";
1313
import * as operation from "../source/operation";
1414
import querySchemas from "./fixtures/query_schemas.json";
1515
import queryServerInformation from "./fixtures/query_server_information.json";
@@ -73,7 +73,7 @@ describe("Session", () => {
7373
const headers = new Promise<Headers>((resolve) => {
7474
server.use(
7575
rest.post("http://ftrack.test/api", (req, res, ctx) => {
76-
resolve(req.headers);
76+
resolve(req.headers as any);
7777
return res.once(ctx.json(getInitialSessionQuery()));
7878
})
7979
);
@@ -165,7 +165,7 @@ describe("Session", () => {
165165
const headers = new Promise<Headers>((resolve) => {
166166
server.use(
167167
rest.post("http://ftrack.test/api", (req, res, ctx) => {
168-
resolve(req.headers);
168+
resolve(req.headers as any);
169169
return res.once(ctx.json(getExampleQuery()));
170170
})
171171
);
@@ -794,3 +794,95 @@ describe("Encoding entities", () => {
794794
]);
795795
});
796796
});
797+
798+
describe("Prepared template tests", () => {
799+
it("escapes single quotes in interpolated values", () => {
800+
const result = expression`It's ${"amazing"} here.`;
801+
expect(result).toBe("It's amazing here.");
802+
});
803+
804+
it("escapes double quotes in interpolated values", () => {
805+
const result = expression`She said, ${'"Hello!"'} to him.`;
806+
expect(result).toBe('She said, \\"Hello!\\" to him.');
807+
});
808+
809+
it("escapes quotes when mixing multiple types", () => {
810+
const result = expression`Quotes: ${`"begin and end'`}.`;
811+
expect(result).toBe(`Quotes: \\"begin and end\\'.`);
812+
});
813+
814+
it("works with multiple interpolated values", () => {
815+
const result = expression`This is ${"first"} and this is ${"second"}.`;
816+
expect(result).toBe("This is first and this is second.");
817+
});
818+
819+
it("works without any interpolated values", () => {
820+
const result = expression`Just a string without any interpolation.`;
821+
expect(result).toBe("Just a string without any interpolation.");
822+
});
823+
824+
it("works with empty string as interpolated value", () => {
825+
const result = expression`This is an ${""} empty value.`;
826+
expect(result).toBe("This is an empty value.");
827+
});
828+
it("handles no arguments", () => {
829+
const result = expression``;
830+
expect(result).toBe("");
831+
});
832+
it("handles backslashes in interpolated values", () => {
833+
const result = expression`This is a backslash: ${"\\"}.`;
834+
expect(result).toBe("This is a backslash: \\.");
835+
});
836+
it("handles unusual characters", () => {
837+
const result = expression`${"æøåßđŋħłøœŧźżšđžčćñé.,;:!?()[]{}<></>+-*/=<>^%&|~©®™µƒ∂∆πΣΩ$€£¥¢₹₽😀😍🤖👍❤️"}`;
838+
expect(result).toBe(
839+
"æøåßđŋħłøœŧźżšđžčćñé.,;:!?()[]{}<></>+-*/=<>^%&|~©®™µƒ∂∆πΣΩ$€£¥¢₹₽😀😍🤖👍❤️"
840+
);
841+
});
842+
});
843+
844+
describe("Prepared template tests", () => {
845+
it("escapes single quotes in interpolated values", () => {
846+
const result = expression`It's ${"amazing"} here.`;
847+
expect(result).toBe("It's amazing here.");
848+
});
849+
850+
it("escapes double quotes in interpolated values", () => {
851+
const result = expression`She said, ${'"Hello!"'} to him.`;
852+
expect(result).toBe('She said, \\"Hello!\\" to him.');
853+
});
854+
855+
it("escapes quotes when mixing multiple types", () => {
856+
const result = expression`Quotes: ${`"begin and end'`}.`;
857+
expect(result).toBe(`Quotes: \\"begin and end\\'.`);
858+
});
859+
860+
it("works with multiple interpolated values", () => {
861+
const result = expression`This is ${"first"} and this is ${"second"}.`;
862+
expect(result).toBe("This is first and this is second.");
863+
});
864+
865+
it("works without any interpolated values", () => {
866+
const result = expression`Just a string without any interpolation.`;
867+
expect(result).toBe("Just a string without any interpolation.");
868+
});
869+
870+
it("works with empty string as interpolated value", () => {
871+
const result = expression`This is an ${""} empty value.`;
872+
expect(result).toBe("This is an empty value.");
873+
});
874+
it("handles no arguments", () => {
875+
const result = expression``;
876+
expect(result).toBe("");
877+
});
878+
it("handles backslashes in interpolated values", () => {
879+
const result = expression`This is a backslash: ${"\\"}.`;
880+
expect(result).toBe("This is a backslash: \\.");
881+
});
882+
it("handles unusual characters", () => {
883+
const result = expression`${"æøåßđŋħłøœŧźżšđžčćñé.,;:!?()[]{}<></>+-*/=<>^%&|~©®™µƒ∂∆πΣΩ$€£¥¢₹₽😀😍🤖👍❤️"}`;
884+
expect(result).toBe(
885+
"æøåßđŋħłøœŧźżšđžčćñé.,;:!?()[]{}<></>+-*/=<>^%&|~©®™µƒ∂∆πΣΩ$€£¥¢₹₽😀😍🤖👍❤️"
886+
);
887+
});
888+
});

0 commit comments

Comments
 (0)