Skip to content

assessAndProtect activates default-on strategies no matched policy requested #17

@Isonimus

Description

@Isonimus

Summary

assessAndProtect() activates protection strategies (and their overlays) that no matched policy requested, because it only flips matched strategies on and leaves the rest at ContentProtector's defaults — several of which default to true.

This contradicts the documented contract ("Legitimate users see no protection overhead; automation and scrapers trigger it automatically" — i.e. activate only what the policies warrant).

Root cause

ContentProtector's constructor defaults several strategies ON (src/core/ContentProtector.ts):

preventSelection: true,
preventScreenshots: true,
// ...
preventDevTools: false,

assessAndProtect() builds protectionOptions by setting only the matched strategy keys to true (src/policy.ts), then new ContentProtector(protectionOptions). Unset keys fall through to the defaults above — so e.g. preventScreenshots activates even when every matched policy only asked for enableWatermark.

Reproduction

await assessAndProtect(el, {
  policies: [{ when: { riskScore: { gte: 0 } }, enable: ['enableWatermark'] }],
});
// Expected: only the watermark is active.
// Actual:   screenshot protection (and its red overlay) is also active,
//           plus any other default-on strategy.

Proposed fix

In assessAndProtect(), initialize all StrategyKey booleans to false in protectionOptions before enabling the matched ones, so the protector reflects exactly the matched set:

const ALL_STRATEGY_KEYS: StrategyKey[] = [
  'preventSelection', 'preventContextMenu', 'preventKeyboardShortcuts',
  'preventPrinting', 'preventScreenshots', 'preventClipboard',
  'enableWatermark', 'preventDevTools', 'preventExtensions',
];
const protectionOptions: ContentProtectionOptions = { targetElement };
for (const k of ALL_STRATEGY_KEYS) protectionOptions[k] = enabledStrategies.has(k);

A test asserting that a single-strategy policy leaves all other strategies inactive would lock this in.

Impact / workaround

Consumers get protections (and overlays) they didn't opt into. Found while wiring Shield into the tindalabs.dev demo; worked around there by calling protector.updateOptions({...}) post-creation to pin the matched set — but the fix belongs in assessAndProtect.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions