diff --git a/packages/cli/src/capture/assetDownloader.test.ts b/packages/cli/src/capture/assetDownloader.test.ts
index 9209e4a685..fa9da1c65d 100644
--- a/packages/cli/src/capture/assetDownloader.test.ts
+++ b/packages/cli/src/capture/assetDownloader.test.ts
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
-import { isPrivateUrl, safeFetch } from "./assetDownloader.js";
+import { isPrivateUrl, safeFetch, toStandaloneSvg } from "./assetDownloader.js";
describe("isPrivateUrl — SSRF denylist (security: F-003)", () => {
it("blocks loopback, private, and metadata IPv4", () => {
@@ -91,3 +91,39 @@ describe("safeFetch — re-validates the denylist on every redirect hop (securit
expect(fetchMock).not.toHaveBeenCalled();
});
});
+
+describe("toStandaloneSvg — scraped inline SVGs must survive as .svg files", () => {
+ it("adds the SVG namespace that outerHTML omits for inline SVG", () => {
+ const inline = '';
+ const out = toStandaloneSvg(inline);
+ expect(out).toContain('xmlns="http://www.w3.org/2000/svg"');
+ // Nothing else may change — the path geometry is the brand mark.
+ expect(out).toContain('');
+ expect(out.endsWith("")).toBe(true);
+ });
+
+ it("leaves an SVG that already declares xmlns untouched", () => {
+ const already = '';
+ expect(toStandaloneSvg(already)).toBe(already);
+ });
+
+ it("declares xmlns:xlink only when an xlink: attribute is actually used", () => {
+ const withXlink = '';
+ expect(toStandaloneSvg(withXlink)).toContain('xmlns:xlink="http://www.w3.org/1999/xlink"');
+ const without = '';
+ expect(toStandaloneSvg(without)).not.toContain("xmlns:xlink");
+ });
+
+ it("is idempotent and preserves attributes on the root", () => {
+ const inline = '';
+ const once = toStandaloneSvg(inline);
+ expect(toStandaloneSvg(once)).toBe(once);
+ for (const attr of ['class="logo"', 'width="120"', 'height="24"', 'fill="currentColor"']) {
+ expect(once).toContain(attr);
+ }
+ });
+
+ it("returns non-SVG input unchanged rather than corrupting it", () => {
+ expect(toStandaloneSvg("
not an svg
")).toBe("not an svg
");
+ });
+});
diff --git a/packages/cli/src/capture/assetDownloader.ts b/packages/cli/src/capture/assetDownloader.ts
index a3f13bb378..67b2c0f750 100644
--- a/packages/cli/src/capture/assetDownloader.ts
+++ b/packages/cli/src/capture/assetDownloader.ts
@@ -17,6 +17,32 @@ function svgContentHashSlug(svgSource: string | Buffer, isLogo: boolean): string
return isLogo ? `logo-${hash}` : `svg-${hash}`;
}
+/**
+ * Make a scraped inline `