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
49 changes: 49 additions & 0 deletions demo-site/src/components/ShadowDomEmbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,34 @@ import { INJECTIONS } from "../data/injection-fixtures";
// second card exercises the Tier 3 adopted-stylesheet path: that
// class only matches via the EasyList generic CSS sheet, which now
// adopts into open shadow roots so the hide applies here too.
//
// A second host below mounts a custom element with a CLOSED shadow root
// to exercise `closed-shadow-root-annotate`. By spec the rules cannot
// see inside; the heuristic only confirms it's there.

const CLOSED_TAG = "abs-closed-widget";

if (typeof customElements !== "undefined" && !customElements.get(CLOSED_TAG)) {
customElements.define(
CLOSED_TAG,
class extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: "closed" });
const inner = document.createElement("div");
inner.style.cssText =
"padding:6px 10px;border:1px solid #7c3aed;background:#f5f3ff;color:#4c1d95;font:13px system-ui;border-radius:4px;";
inner.textContent =
"Closed-shadow widget — contents are invisible to ABS by spec.";
shadow.append(inner);
}
},
);
}

export default function ShadowDomEmbed() {
const hostRef = useRef<HTMLDivElement>(null);
const closedHostRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const host = hostRef.current;
Expand Down Expand Up @@ -75,6 +100,18 @@ export default function ShadowDomEmbed() {
};
}, []);

useEffect(() => {
const host = closedHostRef.current;
if (!host || host.querySelector(CLOSED_TAG)) {
return;
}
const widget = document.createElement(CLOSED_TAG);
host.append(widget);
return () => {
widget.remove();
};
}, []);

return (
<section
aria-label="Shadow-DOM third-party widgets"
Expand All @@ -98,6 +135,18 @@ export default function ShadowDomEmbed() {
ref={hostRef}
className="shadow-host mt-3 rounded border border-dashed border-slate-300 p-3"
/>
<p className="mt-4 text-sm text-stone-700">
The widget below mounts inside a <em>closed</em> shadow root. ABS cannot
reach inside by spec — every rule passes its contents through untouched.
With <code>closed-shadow-root-annotate</code> enabled, a
screen-reader-only landmark is prepended to the document noting that the
page is using closed shadow roots and content here is invisible to the
extension.
</p>
<div
ref={closedHostRef}
className="closed-shadow-host mt-3 rounded border border-dashed border-slate-300 p-3"
/>
</section>
);
}
32 changes: 31 additions & 1 deletion docs/src/content/docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ Any content a page renders inside a closed shadow root — whether ads, chat
widgets, hidden text, or prompt-injection payloads — is invisible to every rule
and will be passed through to the agent untouched. Closed shadow roots are
uncommon outside browser UA shadows and a handful of hardened embeds, but they
are a known gap.
are a known gap. The optional
[Flag Closed Shadow Roots](#flag-closed-shadow-roots-experimental) rule can
heuristically warn the agent at read-time when this gap is in use.

## Indirect prompt injection

Expand Down Expand Up @@ -426,6 +428,34 @@ indirect-prompt-injection delivery surface. Search-engine cloaking has a long
lineage [[19]](#ref-wu-cloaking); the same primitive aimed at LLM crawlers is
the new threat.

#### Flag Closed Shadow Roots (Experimental)

- **ID:** `closed-shadow-root-annotate`
- **Default:** off
- **Top frame only**

Heuristically detect pages that render content inside closed shadow roots and
prepend a screen-reader-only landmark noting that the extension cannot see
inside those shadow trees. Complements the open-shadow-root coverage described
in [Coverage scope](#coverage-scope) above by giving the agent a positive signal
at read-time that a known blind spot is in use on this page.

Detection is necessarily heuristic: by spec, an element with `mode: "closed"` is
indistinguishable from an element with no shadow root at all from outside
JavaScript. The rule looks for the structural shape strongly correlated with
"closed shadow host": an upgraded custom element (hyphenated tag name, defined
in `customElements`) with no light-DOM children, no `host.shadowRoot`, and a
non-zero rendered box. Built-in elements with UA shadow roots (`<input>`,
`<details>`, `<video>`) are filtered out for free — their tag names contain no
hyphen. Declarative shadow DOM (`<template shadowrootmode="closed">`) is
indistinguishable from imperative closed shadows after parsing and is not
separately surfaced.

The landmark says "may contain content ABS cannot see," not "this is definitely
a closed shadow root" — a custom element that renders via canvas, WebGL, or
`::before` background-image with no actual shadow root will trip the heuristic
too. Off by default while the false-positive rate is characterized.

### Visual identity spoofing

#### Flag Spoofed Links
Expand Down
3 changes: 2 additions & 1 deletion extension/data/rule-defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"trust-badge-annotate": false,
"disguised-ad-flag": true,
"encoded-payload-redact": true,
"webdriver-probe-annotate": false
"webdriver-probe-annotate": false,
"closed-shadow-root-annotate": false
}
}
1 change: 1 addition & 0 deletions extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const tabDetections = new Map<number, Map<DetectionKind, DetectionPayload>>();
const DETECTION_KIND_TO_RULE_ID = {
"roach-motel": "roach-motel-annotate",
"webdriver-probe": "webdriver-probe-annotate",
"closed-shadow-root": "closed-shadow-root-annotate",
} as const satisfies Record<DetectionKind, string>;

// Pleasant blue — clearly an extension affordance, not a warning/error.
Expand Down
14 changes: 12 additions & 2 deletions extension/src/lib/detection-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

import type { RoachMotelDifficulty } from "../rules/site-data.generated";

export type DetectionKind = "roach-motel" | "webdriver-probe";
export type DetectionKind =
| "roach-motel"
| "webdriver-probe"
| "closed-shadow-root";

export interface RoachMotelDetectionPayload {
kind: "roach-motel";
Expand All @@ -29,9 +32,16 @@ export interface WebdriverProbeDetectionPayload {
url: string;
}

export interface ClosedShadowRootDetectionPayload {
kind: "closed-shadow-root";
host: string;
url: string;
}

export type DetectionPayload =
| RoachMotelDetectionPayload
| WebdriverProbeDetectionPayload;
| WebdriverProbeDetectionPayload
| ClosedShadowRootDetectionPayload;

export interface RuleDetectionMessage {
type: "rule-detection";
Expand Down
1 change: 1 addition & 0 deletions extension/src/lib/rule-groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const RULE_GROUPS: readonly RuleGroup[] = [
"link-spoof-annotate",
"trust-badge-annotate",
"webdriver-probe-annotate",
"closed-shadow-root-annotate",
],
},
{
Expand Down
Loading
Loading