Skip to content

Clipboard protection, claim by claim ​

WIN-DLP-EPIC-013 asks for clipboard content to be inspected under an explicit policy, in the user's session, without becoming a keylogger - because copy-and-paste is the most common data movement nobody records.

The mechanism is built and the privacy discipline around it is the most carefully argued in the product. What this audit found is that its central claim - that a harmless copy goes no further - was half-tested, and the test said so itself.

Implementation: agent/core/src/clipboard.rs for reading, agent/core/src/session_link.rs for the channel, Agent::queue_clipboard_report in runner.rs for the decision.


1. Off unless a policy asks - and a policy, not a checkbox ​

Reading what somebody copied is the most invasive thing in the product, so turning it on is an act somebody performs, reviews and can point to.

ClaimProved by
A policy naming the clipboard channel turns it onsession_link::clipboard_switch_tests::a_policy_naming_the_clipboard_turns_it_on
A broad policy with no channel scope does notsession_link::clipboard_switch_tests::a_broad_policy_with_no_channel_scope_does_not
No policies means offsession_link::clipboard_switch_tests::no_policies_means_off
Removing the policy turns it off againsession_link::clipboard_switch_tests::removing_the_policy_turns_it_off_again
The clipboard and browser switches are independentsession_link::clipboard_switch_tests::the_two_switches_are_independent
Every state that is not live says whysession_link::clipboard_switch_tests::every_state_that_is_not_live_says_why
The channel is live only when both halves are truesession_link::service::tests::the_clipboard_channel_is_live_only_when_both_halves_are
A copy is dropped if the tenant switched it offsession_link::service::tests::a_clipboard_is_dropped_when_the_tenant_switched_it_off
The switch is a policy naming the channel, not a checkboxthe_switch_is_a_policy_naming_the_channel_and_not_a_checkbox (agent/core/tests/clipboard_flow.rs)

The second row is the one that matters most and is easiest to get wrong. Somebody writing "block card numbers leaving this company" with an empty channel scope has not asked to read everybody's clipboard, and the code treats an empty scope as "no constraint" everywhere else. This is the one place that reading would be indefensible, and it is tested.

The drop-if-switched-off row is the other half: the switch is the tenant's, and a session agent that ignores its Welcome and reports anyway is not trusted to honour it. The agent drops the report rather than believing the peer.

2. Reading, without becoming a keylogger ​

ClaimProved by
The same copy is reported onceclipboard::tests::the_same_copy_is_reported_once
Copying something else and back is two copiesclipboard::tests::copying_something_else_and_back_is_two_copies
Switching reporting on does not report what was already thereclipboard::tests::switching_reporting_off_and_on_does_not_report_what_was_already_there
An image on the clipboard is its own answer, not nothingclipboard::tests::an_image_on_the_clipboard_is_its_own_answer_not_nothing
A platform that cannot read a clipboard says whyclipboard::tests::a_platform_that_cannot_read_a_clipboard_says_why
A bitmap is an image, and every other format only not textclipboard::tests::a_bitmap_is_an_image_and_every_other_format_is_only_not_text
A copied image is a fact, recorded only under a policy naming the clipboard, audit only, once a minutenothing_sensitive_leaks::a_copied_image_is_recorded_as_a_fact_only_where_a_policy_names_the_clipboard, runner::tests::an_image_copy_is_only_monitored_where_a_text_copy_would_warn, runner::tests::an_image_copy_is_recorded_at_most_once_a_minute

The third row is a privacy property rather than a correctness one: turning the switch on must not sweep up whatever happened to be on the clipboard from before, which the person copied under no policy at all.

The distinction between rows one and two is what makes this a clipboard monitor and not a sampler. Reporting changes means a person who copies the same thing twice produces two copies; a naive "poll and compare to last" would report one.

3. The claim the whole feature rests on ​

From runner.rs, in the branch where a copy matched nothing:

"Nothing sensitive was copied. The content is dropped here and never reaches the queue, the disk or the network - which is the difference between a DLP agent and a keylogger."

agent/core/tests/clipboard_flow.rs proves a copy crosses the channel and that a harmless one matches no detector, and its own comment admits the limit: "the assertion here is only the first half". It cannot construct a running agent, so it cannot follow the content past the scan.

tests/clipboard_privacy.sh is the second half (DLP-127). A real agent, a real published policy naming the clipboard, a real session connection, two copies - one harmless, one carrying a card number - and then:

ClaimHow
Exactly one clipboard event reaches the serverCounted in the database, scoped to the run's tenant. One says two things at once: the sensitive copy produced an event, and the harmless copy did not
The stored event carries no copy of the matched valueThe event row is searched for the card number
The harmless copy is in no file the agent ownsEvery file under the state directory, and the agent's log, searched for the words that were copied
The matched value is in no file eitherThe same search, for the card number, which reached the queue as a redacted preview and must not have reached it as itself

That third row is the one that catches the regression nobody means to write: a tracing::debug!(content = %report.content) added while chasing something else would put every copy anybody makes into a log file, and every test in the repository would still pass.

Two things went wrong while writing it, both worth recording, because both would have made it a test that passed without proving anything:

  1. The session channel opens before the first check-in, with reporting off, because no policy has arrived yet. Adopting the bundle replaces the server - that is how the switch reaches a connected session agent - so a client that connects too early is told the clipboard is off, has its copies dropped, and is disconnected. The test now waits for the bundle, not for the socket.
  2. Counting clipboard events without scoping to the tenant inherits every event any previous run left behind. The e2e database is shared between scripts.

4. What is absent ​

CapabilityState
Reading a clipboard on LinuxAbsent. dlp-agent session connects and runs, and there is nothing behind it: reading needs an X11 or Wayland client. The subcommand says so and reports nothing. DLP-180
Preventing a pasteAbsent, and it is not obviously possible - DLP-182 is the spike asking whether a paste can be prevented at all
Attributing a copy to the application it came fromBuilt: the clipboard owner the session agent captured (pid and start time) is resolved by the service as SYSTEM, checked against its start time, and that verified name is what an application-scoped policy is evaluated against (DLP-181, DLP-1132). The session agent's own foreground_app is never believed, and with no verified owner such a policy matches nobody. Not yet run on a Windows desktop
A clipboard with a real interactive session on WindowsNever run. DLP-1176

5. What is not covered ​

  • Nothing here has been run with a person at a keyboard. The Windows pipe, the handshake and the clipboard reader compile for Windows, and the CI job written to exercise them on a real Windows kernel has not yet passed: its pipe tests fail on the runner (DLP-1489, Windows CI status). Even that job could not supply a desktop session and somebody copying something. The capability follows the live connection, so an endpoint where this has not happened reports clipboard as unavailable rather than claiming it.
  • tests/clipboard_privacy.sh drives the channel directly, over the unix socket, rather than through dlp-agent session - because the session agent reads a real clipboard, and a CI runner has none. It tests the agent's half of the contract, which is the half the privacy claim lives in.
  • Image content is detected and not read. An image on the clipboard is reported as its own answer. Since DLP-1168 it is recorded as an audit event (destination.kind = "clipboard_image"), under a policy that names the clipboard channel, at most once a minute. OCR of it would be DLP-25.

Statements here describe what is built and tested; planned work is marked as planned.