Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright (c) 2026 PixieBrix, Inc.
// Licensed under PolyForm Shield 1.0.0 — see LICENSE.

// Property-based tests for `attribute-injection-sanitize`. Fuzzes across
// the allowlisted attributes × the shared injection fixture set, plus
// random benign prefix/suffix noise to confirm the regex set still fires
// when the payload is embedded inside surrounding text. A negative
// property covers attributes outside the allowlist (the rule must not
// touch them) and another covers enabled-input `value` (only `disabled`
// inputs get their value stripped).

import fc from "fast-check";

import { attributeInjectionSanitizeRule } from "../attribute-injection-sanitize";
import { FIXTURES } from "./injection-fixtures";

const ALLOWLISTED_ATTRS = [
"aria-label",
"aria-description",
"alt",
"title",
"placeholder",
"data-tooltip",
] as const;

const TAG_FOR_ATTR: Record<(typeof ALLOWLISTED_ATTRS)[number], string> = {
"aria-label": "button",
"aria-description": "div",
alt: "img",
title: "span",
placeholder: "input",
"data-tooltip": "div",
};

// Subset of FIXTURES that unambiguously match a current injection
// pattern. Skip `BENIGN_LLM` etc. which are intentionally non-matching.
const ADVERSARIAL = fc.constantFrom(
FIXTURES.IGNORE_HACKED,
FIXTURES.DISREGARD,
FIXTURES.DAN,
FIXTURES.DEV_MODE,
FIXTURES.NEW_INSTRUCTIONS,
FIXTURES.OVERRIDE_GUARDRAILS,
FIXTURES.PLEASE_IGNORE,
FIXTURES.IGNORE_ALL,
);

// Hex-character noise — can't form English imperatives so it won't
// accidentally trip an injection pattern when used as prefix/suffix.
// We require word-boundary separation from the payload (trailing space
// on prefix, leading space on suffix) because the injection patterns
// anchor with `\b`; concatenating a word character directly against
// the payload defeats the boundary by design.
const prefixArb = fc.oneof(
fc.constant(""),
fc.stringMatching(/^[0-9a-f]{1,30}$/).map((s) => `${s} `),
);
const suffixArb = fc.oneof(
fc.constant(""),
fc.stringMatching(/^[0-9a-f]{1,30}$/).map((s) => ` ${s}`),
);

const attributeArb = fc.constantFrom(...ALLOWLISTED_ATTRS);

function buildElement(
tag: string,
attribute: string,
value: string,
): HTMLElement {
document.body.innerHTML = "";
const element = document.createElement(tag);
element.setAttribute(attribute, value);
document.body.append(element);
return element;
}

afterEach(() => {
attributeInjectionSanitizeRule.teardown();
document.body.innerHTML = "";
});

describe("attribute-injection-sanitize (property)", () => {
it("strips any allowlisted attribute when its value carries an injection payload, possibly embedded in surrounding noise", () => {
fc.assert(
fc.property(
attributeArb,
ADVERSARIAL,
prefixArb,
suffixArb,
(attribute, payload, prefix, suffix) => {
const tag = TAG_FOR_ATTR[attribute];
const element = buildElement(
tag,
attribute,
`${prefix}${payload}${suffix}`,
);
attributeInjectionSanitizeRule.apply(document.body);
expect(element.hasAttribute(attribute)).toBe(false);
},
),
);
});

it("preserves non-allowlisted attributes even when they carry an injection payload", () => {
fc.assert(
fc.property(ADVERSARIAL, (payload) => {
// `data-foo` and `name` are not in the allowlist. The rule
// should treat them as page data and leave them alone.
const element = buildElement("div", "data-foo", payload);
element.setAttribute("name", payload);
attributeInjectionSanitizeRule.apply(document.body);
expect(element.dataset.foo).toBe(payload);
expect(element.getAttribute("name")).toBe(payload);
}),
);
});

it("strips value on a disabled input carrying injection, leaves enabled inputs alone", () => {
fc.assert(
fc.property(ADVERSARIAL, (payload) => {
document.body.innerHTML = "";
const disabled = document.createElement("input");
disabled.setAttribute("disabled", "");
disabled.setAttribute("value", payload);
const enabled = document.createElement("input");
enabled.setAttribute("value", payload);
document.body.append(disabled, enabled);

attributeInjectionSanitizeRule.apply(document.body);

expect(disabled.hasAttribute("value")).toBe(false);
expect(enabled.getAttribute("value")).toBe(payload);
}),
);
});

it("preserves clean (non-injection) values on allowlisted attributes", () => {
const cleanArb = fc.constantFrom(
"Add to cart",
"Product photo",
"Posted 3 days ago",
"Search products",
"Apply coupon",
"Free shipping over $25",
);
fc.assert(
fc.property(attributeArb, cleanArb, (attribute, value) => {
const tag = TAG_FOR_ATTR[attribute];
const element = buildElement(tag, attribute, value);
attributeInjectionSanitizeRule.apply(document.body);
expect(element.getAttribute(attribute)).toBe(value);
}),
);
});
});
134 changes: 134 additions & 0 deletions extension/src/rules/__tests__/meta-injection-strip.property.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) 2026 PixieBrix, Inc.
// Licensed under PolyForm Shield 1.0.0 — see LICENSE.

// Property-based tests for `meta-injection-strip`. Confirms that the
// remove-vs-blank asymmetry holds across the injection fixture set:
// `<meta>` is removed entirely (a content-less meta has no value), while
// `<title>` is kept as an element with empty text (so `document.title`
// returns "" rather than the payload — code reading title still works).

import fc from "fast-check";

import { metaInjectionStripRule } from "../meta-injection-strip";
import { FIXTURES } from "./injection-fixtures";

const ADVERSARIAL = fc.constantFrom(
FIXTURES.IGNORE_HACKED,
FIXTURES.DISREGARD,
FIXTURES.DAN,
FIXTURES.DEV_MODE,
FIXTURES.NEW_INSTRUCTIONS,
FIXTURES.OVERRIDE_GUARDRAILS,
FIXTURES.PLEASE_IGNORE,
FIXTURES.IGNORE_ALL,
);

// Common meta naming conventions — `name=` (HTML), `property=` (OG /
// Twitter), `itemprop=` (schema.org), `http-equiv=` (legacy). The rule
// must remove the meta regardless of which attribute names it.
const NAME_ATTRS = fc.constantFrom(
"name",
"property",
"itemprop",
"http-equiv",
);
const NAME_VALUES = fc.constantFrom(
"description",
"og:description",
"twitter:description",
"og:title",
"keywords",
"summary",
);

function resetHead(): void {
for (const element of [
...document.head.querySelectorAll("meta"),
...document.head.querySelectorAll("title"),
]) {
element.remove();
}
}

afterEach(() => {
metaInjectionStripRule.teardown();
document.body.innerHTML = "";
resetHead();
});

describe("meta-injection-strip (property)", () => {
it("removes any <meta content> carrying injection text regardless of name attribute", () => {
fc.assert(
fc.property(
ADVERSARIAL,
NAME_ATTRS,
NAME_VALUES,
(payload, nameAttribute, nameValue) => {
resetHead();
const meta = document.createElement("meta");
meta.setAttribute(nameAttribute, nameValue);
meta.setAttribute("content", payload);
document.head.append(meta);

metaInjectionStripRule.apply(document.body);

const remaining = document.head.querySelector(
`meta[${nameAttribute}="${nameValue}"]`,
);
expect(remaining).toBeNull();
},
),
);
});

it("blanks <title> text but keeps the element when title carries injection", () => {
fc.assert(
fc.property(ADVERSARIAL, (payload) => {
resetHead();
const title = document.createElement("title");
title.textContent = payload;
document.head.append(title);

metaInjectionStripRule.apply(document.body);

// Element survives so consumers of `document.title` still get
// a string (empty), not undefined.
const remaining = document.head.querySelector("title");
expect(remaining).not.toBeNull();
expect(remaining?.textContent).toBe("");
expect(document.title).toBe("");
}),
);
});

it("preserves benign meta and title content", () => {
const cleanArb = fc.constantFrom(
"Shop the latest sneakers and apparel",
"RiverMart — everything from cookware to compute",
"Free returns on orders over $25",
FIXTURES.BENIGN_LLM,
);
fc.assert(
fc.property(cleanArb, cleanArb, (metaContent, titleText) => {
resetHead();
const meta = document.createElement("meta");
meta.setAttribute("name", "description");
meta.setAttribute("content", metaContent);
const title = document.createElement("title");
title.textContent = titleText;
document.head.append(meta, title);

metaInjectionStripRule.apply(document.body);

expect(
document.head
.querySelector('meta[name="description"]')
?.getAttribute("content"),
).toBe(metaContent);
expect(document.head.querySelector("title")?.textContent).toBe(
titleText,
);
}),
);
});
});
Loading