Host DLP sensors: OS-specific text sources (clipboard, keyboard/evdev, PTY) behind a testable TextSource seam, feeding infrastructure-dlp-detect and emitting masked DlpFindingEnvelopes via infrastructure-dlp. Masked-only by default; the raw-capture path is behind the `raw-capture` feature.
| Tier | operations |
| Role | unclassified (baselined) |
| Path | crates/operations/dlp-sensor |
| Edition | 2021 |
| Targets | clipboard_verify, keyboard_verify, pty_verify, screen_ocr_demo, operations_dlp_sensor, screen_pipeline |
| Public items | 31 across 6 modules |
| Tests | 12 |
What it is for
operations-dlp-sensor — host DLP sensors.
OS-specific text sources implement the source::TextSource seam; the pipeline runs each chunk through infrastructure_dlp_detect and emits masked infrastructure_dlp::DlpFindingEnvelopes. Masked-only by default; the raw path is behind the raw-capture feature.
The detector set follows infrastructure-dlp-detect policy defaults — it grew in Sprint 3.6 to include the secret-material classes (api-key, private-key, JWT, email, high-entropy secret) alongside the original PII classes. See pipeline::scan_chunk for how to pin or narrow it.
Sources (feature-gated so a minimal build pulls no OS deps):
clipboard(clipboardfeature, arboard) — paste detection.- keyboard (
keyboardfeature, evdev) — physical typing (added incrementally).
Capabilities
ClipboardSource
Clipboard text source (feature clipboard, via arboard; X11/Wayland).
| Item |
|---|
pub struct ClipboardSource |
ClipboardSource :: fn new() -> Result <Self, arboard::Error> |
ClipboardSource :: fn with_poll_interval(mut self, d : Duration) -> Self |
ClipboardSource :: fn read_new(& mut self) -> Option <String> |
ClipboardSource :: fn next_chunk(& mut self) -> Option <String> |
keyboard (other)
Physical-keyboard text source (feature keyboard, Linux evdev).
| Item |
|---|
pub const MAX_FIELD: usize |
KeyboardSource
Physical-keyboard text source (feature keyboard, Linux evdev).
| Item |
|---|
pub struct KeyboardSource |
KeyboardSource :: fn from_path <P : AsRef <Path>>(path : P) -> io::Result <Self> |
KeyboardSource :: fn first_keyboard() -> io::Result <Self> |
KeyboardSource :: fn next_chunk(& mut self) -> Option <String> |
SensorCtx
The scan pipeline: text chunk → detector → masked envelopes.
| Item |
|---|
pub struct SensorCtx<'a> |
fn scan_chunk(text : & str, ctx : & SensorCtx <'_>, seq : u64, mut mint : impl FnMut() ->(String, i64),) -> Vec <DlpFindingEnvelope> |
fn run <S, M, E>(mut source : S, ctx : & SensorCtx <'_>, mut mint : M, mut emit : E) -> u64 where S : TextSource, M : FnMut() ->(String, i64), E : FnMut(DlpFindingEnvelope), |
fn run_with_raw <S, M, E, R>(mut source : S, ctx : & SensorCtx <'_>, mut mint : M, mut emit : E, mut emit_raw : R,) -> u64 where S : TextSource, M : FnMut() ->(String, i64), E : FnMut(DlpFindingEnvelope), R : FnMut(infrastructure_dlp::capture::RawCapture), |
LineSource
PTY / line-oriented text source (T9).
| Item |
|---|
pub struct LineSource<R : Read> |
LineSource<R>
PTY / line-oriented text source (T9).
| Item |
|---|
LineSource<R> :: fn new(reader : R) -> Self |
LineSource<R> :: fn next_chunk(& mut self) -> Option <String> |
PtySession
PTY / line-oriented text source (T9).
| Item |
|---|
pub struct PtySession |
fn run_under_pty(argv : & & str) -> std::io::Result <PtySession> |
screen (other)
Screen-capture + OCR text source (feature screen).
| Item |
|---|
pub const DEFAULT_INTERVAL: Duration |
ScreenOcrSource
Screen-capture + OCR text source (feature screen).
| Item |
|---|
pub struct ScreenOcrSource<E : LocalOcrEngine> |
ScreenOcrSource<E>
Screen-capture + OCR text source (feature screen).
| Item |
|---|
ScreenOcrSource<E> :: fn new(engine : E) -> std::io::Result <Self> |
ScreenOcrSource<E> :: fn with_interval(mut self, interval : Duration) -> Self |
ScreenOcrSource<E> :: fn with_max_frames(mut self, n : u64) -> Self |
ScreenOcrSource<E> :: fn with_capture(mut self, f : impl FnMut() -> Result <SensitiveFrame, CaptureError> + Send + 'static,) -> Self |
ScreenOcrSource<E> :: fn next_chunk(& mut self) -> Option <String> |
source (other)
The TextSource seam — the single interface every sensor implements so the
| Item |
|---|
fn chunk_hash(s : & str) -> u64 |
FixtureSource
The TextSource seam — the single interface every sensor implements so the
| Item |
|---|
pub struct FixtureSource |
FixtureSource :: fn new(chunks : Vec <String>) -> Self |
FixtureSource :: fn next_chunk(& mut self) -> Option <String> |
TextSource
The TextSource seam — the single interface every sensor implements so the
| Item |
|---|
pub trait TextSource |
How to use it
From `examples/clipboard_verify.rs`:
use infrastructure_dlp::capture::CaptureMode;
use infrastructure_dlp::envelope::Source;
use operations_dlp_sensor::clipboard::ClipboardSource;
use operations_dlp_sensor::pipeline::{scan_chunk, SensorCtx};
fn main() {
let mut cb = ClipboardSource::new().expect("open clipboard (need DISPLAY)");
let Some(text) = cb.read_new() else {
println!("clipboard empty/unchanged — nothing to scan");
return;
};
let ctx = SensorCtx {
tenant_id: "verify".into(),
device_id: "host".into(),
source: Source::Clipboard,
capture_mode: CaptureMode::MaskedOnly,
From `examples/keyboard_verify.rs`:
use std::thread;
use std::time::Duration;
use evdev::uinput::VirtualDeviceBuilder;
use evdev::{AttributeSet, EventType, InputEvent, Key};
use infrastructure_dlp::capture::CaptureMode;
use infrastructure_dlp::envelope::Source;
use operations_dlp_sensor::keyboard::KeyboardSource;
use operations_dlp_sensor::pipeline::{scan_chunk, SensorCtx};
use operations_dlp_sensor::TextSource;
fn key_for(c: char) -> Key {
match c {
'0' => Key::KEY_0,
'1' => Key::KEY_1,
'2' => Key::KEY_2,
Module structure
operations_dlp_sensor
clipboardkeyboardpipelineptyscreensource
flowchart TD n_operations_dlp_sensor["operations_dlp_sensor"] n_operations_dlp_sensor --> n_clipboard["clipboard"] n_operations_dlp_sensor --> n_keyboard["keyboard"] n_operations_dlp_sensor --> n_pipeline["pipeline"] n_operations_dlp_sensor --> n_pty["pty"] n_operations_dlp_sensor --> n_screen["screen"] n_operations_dlp_sensor --> n_source["source"]
Public surface
`clipboard`
| Item | What it is |
|---|---|
pub struct ClipboardSource | Polls the OS clipboard and yields newly-observed non-empty text. |
ClipboardSource :: fn new() -> Result <Self, arboard::Error> | Open the OS clipboard |
ClipboardSource :: fn with_poll_interval(mut self, d : Duration) -> Self | Override the poll interval used by TextSource::next_chunk. |
ClipboardSource :: fn read_new(& mut self) -> Option <String> | Non-blocking single check: Some(text) if the clipboard holds new, non-empty text since the last observed content, else None |
ClipboardSource :: fn next_chunk(& mut self) -> Option <String> | — |
`keyboard`
| Item | What it is |
|---|---|
pub const MAX_FIELD: usize | Max characters buffered for one field before older input is dropped. |
pub struct KeyboardSource | Reads one keyboard device and yields completed fields as text chunks. |
KeyboardSource :: fn from_path <P : AsRef <Path>>(path : P) -> io::Result <Self> | Open a specific input device (e.g |
KeyboardSource :: fn first_keyboard() -> io::Result <Self> | Open the first keyboard-like device (supports KEY_ENTER and KEY_A). |
KeyboardSource :: fn next_chunk(& mut self) -> Option <String> | — |
`pipeline`
| Item | What it is |
|---|---|
pub struct SensorCtx<'a> | Per-run context supplied by the host |
fn scan_chunk(text : & str, ctx : & SensorCtx <'_>, seq : u64, mut mint : impl FnMut() ->(String, i64),) -> Vec <DlpFindingEnvelope> | Scan one text chunk and build masked envelopes |
fn run <S, M, E>(mut source : S, ctx : & SensorCtx <'_>, mut mint : M, mut emit : E) -> u64 where S : TextSource, M : FnMut() ->(String, i64), E : FnMut(DlpFindingEnvelope), | Drive a TextSource to exhaustion, invoking emit for every masked envelope |
fn run_with_raw <S, M, E, R>(mut source : S, ctx : & SensorCtx <'_>, mut mint : M, mut emit : E, mut emit_raw : R,) -> u64 where S : TextSource, M : FnMut() ->(String, i64), E : FnMut(DlpFindingEnvelope), R : FnMut(infrastructure_dlp::capture::RawCapture), | Like run, but under CaptureMode::Raw it ALSO hands each raw chunk to emit_raw as a infrastructure_dlp::capture::RawCapture (feature raw-capture) |
`pty`
| Item | What it is |
|---|---|
pub struct LineSource<R : Read> | Yields non-empty, newline-delimited chunks from any reader. |
LineSource<R> :: fn new(reader : R) -> Self | Wrap a reader (a PTY master, a pipe, or a test cursor). |
LineSource<R> :: fn next_chunk(& mut self) -> Option <String> | — |
pub struct PtySession | A running child plus a LineSource over its PTY output (feature pty). |
fn run_under_pty(argv : & & str) -> std::io::Result <PtySession> | Run argv under a real pseudo-terminal and return a PtySession whose output yields the session's lines for scanning (feature pty). |
`screen`
| Item | What it is |
|---|---|
pub const DEFAULT_INTERVAL: Duration | Default interval between screen captures. |
pub struct ScreenOcrSource<E : LocalOcrEngine> | A screen-capture + local-OCR TextSource. |
ScreenOcrSource<E> :: fn new(engine : E) -> std::io::Result <Self> | Build a source that captures the primary monitor with the given local engine |
ScreenOcrSource<E> :: fn with_interval(mut self, interval : Duration) -> Self | Set the capture interval (0 = capture as fast as possible). |
ScreenOcrSource<E> :: fn with_max_frames(mut self, n : u64) -> Self | Stop after n captures (unbounded by default) |
ScreenOcrSource<E> :: fn with_capture(mut self, f : impl FnMut() -> Result <SensitiveFrame, CaptureError> + Send + 'static,) -> Self | Inject a custom capture function (tests, or a non-primary monitor). |
ScreenOcrSource<E> :: fn next_chunk(& mut self) -> Option <String> | — |
`source`
| Item | What it is |
|---|---|
pub trait TextSource | A source of scannable text chunks (one clipboard paste, one typed field, one PTY line, …) |
pub struct FixtureSource | In-memory source for unit tests — yields preset chunks, no I/O |
FixtureSource :: fn new(chunks : Vec <String>) -> Self | Build from a list of chunks. |
FixtureSource :: fn next_chunk(& mut self) -> Option <String> | — |
fn chunk_hash(s : & str) -> u64 | Stable non-storing dedup helper: a 64-bit hash of a chunk, so a source can skip re-emitting identical content without retaining the text itself (R5). |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
ScreenOcrSource | screen::ScreenOcrSource |
{FixtureSource,TextSource} | source::{FixtureSource,TextSource} |
{scan_chunk,SensorCtx} | pipeline::{scan_chunk,SensorCtx} |
Boundary
Reaches into infrastructure.
Shares tier operations with 40 other crates: operations-approval-workflow, operations-assessments, operations-block-imaging, operations-boot-media, operations-browser-agent-worker, operations-camera-discovery, operations-camera-liveview, operations-camera-registry, … (40 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | operations |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/operations/dlp-sensor |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_operations["operations"] --> n_infrastructure["infrastructure"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `infrastructure-dlp` | infrastructure | no | always |
| `infrastructure-dlp-detect` | infrastructure | no | always |
| `infrastructure-ocr-engine` | infrastructure | yes | always |
| `infrastructure-screen-capture` | infrastructure | yes | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
arboard | ^3 | — | yes | always |
evdev | ^0.12 | — | yes | always |
portable-pty | ^0.8 | — | yes | always |
tokio | ^1 | full, rt, process, io-util, time | yes | always |
Development, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `infrastructure-dlp-detect` | infrastructure | no | always |
| `infrastructure-ocr-tesseract` | infrastructure | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
image | ^0.25 | png | no | always |
serde_json | ^1 | — | no | always |
Build. None.
Depended on by. 1 workspace crate.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_operations_dlp_agent["operations-dlp-agent"] -->|uses| SELF SELF["operations-dlp-sensor"] SELF -->|development| n_infrastructure_dlp_detect["infrastructure-dlp-detect"] SELF -->|development| n_infrastructure_ocr_tesseract["infrastructure-ocr-tesseract"] SELF -->|runtime| n_infrastructure_dlp["infrastructure-dlp"] SELF -->|runtime| n_infrastructure_dlp_detect["infrastructure-dlp-detect"] SELF -->|runtime| n_infrastructure_ocr_engine["infrastructure-ocr-engine"] SELF -->|runtime| n_infrastructure_screen_capture["infrastructure-screen-capture"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
| Feature | Enables | On by default |
|---|---|---|
clipboard | dep:arboard | yes |
default | clipboard | yes |
keyboard | dep:evdev | no |
pty | dep:portable-pty | no |
raw-capture | — | no |
screen | dep:infrastructure-screen-capture, dep:infrastructure-ocr-engine, dep:tokio | no |
flowchart LR n_clipboard["clipboard"] --> n_dep_arboard["dep:arboard"] n_default["default"] --> n_clipboard["clipboard"] n_keyboard["keyboard"] --> n_dep_evdev["dep:evdev"] n_pty["pty"] --> n_dep_portable_pty["dep:portable-pty"] n_raw_capture["raw-capture"] n_screen["screen"] --> n_dep_infrastructure_screen_capture["dep:infrastructure-screen-capture"] n_screen["screen"] --> n_dep_infrastructure_ocr_engine["dep:infrastructure-ocr-engine"] n_screen["screen"] --> n_dep_tokio["dep:tokio"]
Targets
| Kind | Name | Source |
|---|---|---|
| example | clipboard_verify | `examples/clipboard_verify.rs` |
| example | keyboard_verify | `examples/keyboard_verify.rs` |
| example | pty_verify | `examples/pty_verify.rs` |
| example | screen_ocr_demo | `examples/screen_ocr_demo.rs` |
| lib | operations_dlp_sensor | `src/lib.rs` |
| test | screen_pipeline | `tests/screen_pipeline.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | yes |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
1 workspace crate depends on this one: operations-dlp-agent.
Verification
| Kind | Count |
|---|---|
| Unit tests | 11 |
| Integration tests | 1 |
| Examples | 4 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
clipboard | 1 | 1 | 1 |
keyboard | 2 | 1 | 0 |
pipeline | 4 | 3 | 3 |
pty | 3 | 1 | 0 |
screen | 2 | 1 | 1 |
source | 3 | 1 | 1 |
What the tests establish, by name:
screen_source_real_ocr_emits_masked_screen_envelope—tests/screen_pipeline.rskeymap_digits_and_dash—src/keyboard.rsfingerprinter_wires_through—src/pipeline.rsraw_mode_emits_raw_masked_mode_does_not—src/pipeline.rsrun_over_fixture_counts_and_emits_masked—src/pipeline.rsscan_chunk_masks_and_never_emits_raw—src/pipeline.rsline_source_yields_nonempty_lines_then_eof—src/pty.rscapture_failure_ends_stream_fail_closed—src/screen.rscaptures_ocrs_and_the_text_masks_via_detect—src/screen.rsmasked_only_screen_envelope_leaks_no_raw_pan—src/screen.rsfixture_yields_then_ends—src/source.rshash_is_stable_and_discriminating—src/source.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 26 | 31 |
Public modules with a //! block | 6 | 6 |
pie showData
title Public items with rustdoc
"Documented" : 26
"No rustdoc detected" : 5
Metrics
| Metric | Value |
|---|---|
| Rust source files | 7 |
| Source lines | 841 |
| Code lines | 609 |
| Public API items | 31 |
| Public modules | 6 |
| Tests | 12 |
| Examples | 4 |
| Cargo features | 6 |
| Direct runtime dependencies | 8 |
| Workspace reverse dependencies | 1 |
pie showData
title Public API by kind
"constant" : 2
"function" : 5
"method" : 16
"struct" : 7
"trait" : 1
pie showData
title Rust source composition
"Code" : 609
"Blank or comment" : 232
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.