security: workbook path allowlist + stronger image polyglot validation (#54)
- dispo workbook imports are pinned to DISPO_IMPORT_DIR (default ./imports): tRPC input rejects absolute paths and .. segments, runtime reader re-validates containment via path.relative. Closes a path-traversal class that reached ExcelJS CVEs through admin/compromised tokens. - image validator now checks the full 8-byte PNG magic, enforces PNG IEND and JPEG EOI trailers, scans the decoded buffer for markup polyglot markers (<script, <svg, <iframe, javascript:, onerror=, ...), and explicitly rejects SVG. Provider-generated covers (DALL-E, Gemini) run through the same validator before persistence — an untrusted upstream cannot smuggle a stored-XSS payload past us. - added image-validation.test.ts and tightened documentation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,7 @@ vi.mock("../ai-client.js", async (importOriginal) => {
|
||||
createDalleClient: vi.fn(() => ({
|
||||
images: {
|
||||
generate: vi.fn().mockResolvedValue({
|
||||
data: [{ b64_json: "ZmFrZQ==" }],
|
||||
data: [{ b64_json: "iVBORw0KGgoAAAAASUVORK5CYII=" }],
|
||||
}),
|
||||
},
|
||||
})),
|
||||
@@ -49,10 +49,7 @@ vi.mock("../ai-client.js", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import {
|
||||
createToolContext,
|
||||
executeTool,
|
||||
} from "./assistant-tools-project-media-test-helpers.js";
|
||||
import { createToolContext, executeTool } from "./assistant-tools-project-media-test-helpers.js";
|
||||
|
||||
describe("assistant project cover generation tools", () => {
|
||||
beforeEach(() => {
|
||||
@@ -60,7 +57,8 @@ describe("assistant project cover generation tools", () => {
|
||||
});
|
||||
|
||||
it("routes project cover generation through the real project router path", async () => {
|
||||
const projectFindUnique = vi.fn()
|
||||
const projectFindUnique = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({
|
||||
id: "project_1",
|
||||
name: "Project One",
|
||||
@@ -84,7 +82,7 @@ describe("assistant project cover generation tools", () => {
|
||||
});
|
||||
const projectUpdate = vi.fn().mockResolvedValue({
|
||||
id: "project_1",
|
||||
coverImageUrl: "data:image/png;base64,ZmFrZQ==",
|
||||
coverImageUrl: "data:image/png;base64,iVBORw0KGgoAAAAASUVORK5CYII=",
|
||||
});
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
@@ -119,7 +117,7 @@ describe("assistant project cover generation tools", () => {
|
||||
|
||||
expect(projectUpdate).toHaveBeenCalledWith({
|
||||
where: { id: "project_1" },
|
||||
data: { coverImageUrl: "data:image/png;base64,ZmFrZQ==" },
|
||||
data: { coverImageUrl: "data:image/png;base64,iVBORw0KGgoAAAAASUVORK5CYII=" },
|
||||
});
|
||||
expect(projectFindUnique).toHaveBeenCalledWith({
|
||||
where: { id: "project_1" },
|
||||
|
||||
Reference in New Issue
Block a user