Skip to content

File DLP copy correlation, part 3: process taint (#334) - #378

Merged
neamul602 merged 3 commits into
devfrom
feat/334-process-taint
Nov 2, 2026
Merged

neamul602 merged 3 commits into
devfrom
feat/334-process-taint

Conversation

@neamul602

Copy link
Copy Markdown
Contributor

Part 3 of 3 for #334 (parts 1 and 2: #376, #377), following the merged backend contract (BrainStation-23/Compliance-OS#572): process_taint. Linux; off by default.

What it does

A file written to a monitored drive that can't be judged on its own (unreadable, for example a password-protected archive built straight onto the stick; or an archive) is judged by the files its writer read just before.

  • Watching opens: a second fanotify group gets FAN_OPEN notifications for the mount holding /home, only while taint is on and a drive or share is watched. It's opened the first time it's needed. It has its own reader, so a flood of opens (a build, npm install) can only overflow its own queue, never the drive's write events.
  • Recording: each open under /home costs a readlink and an fstat. The path and identity go into an in-memory store: 32 per process, 4096 processes, for taint_window_seconds. Nothing is scanned at open time.
  • Correlating happens when a written file matched nothing and couldn't be judged on its own:
    • up to 8 sources are checked, newest first;
    • only opens after the process started count, so a reused PID inherits nothing;
    • each source is reopened with O_NOFOLLOW, checked to be the same file (device and inode), and scanned through the cache like any file;
    • a match replaces the fail-mode events with correlated ones (correlation: {method: taint, source_sha256, source_name}, verdict_source: correlation), and the after-write action (File DLP copy correlation, part 1: settings, after-write action, capabilities (#334) #376) applies to the written file.
  • No false blame: a readable file that matched nothing is never judged by what its writer read earlier. Saving an unrelated document after opening a sensitive one isn't flagged.
  • capabilities.copy_correlation.process_taint is supported on Linux. Health reports processes tracked and open events lost to overflow.

Measured cost

In a privileged container: 100,000 file opens while a drive was mounted and taint was on.

With the open watch Without
Workload wall time 1.054 s 0.826 s
Agent CPU 520 ms (~5 µs per open) 7 ms
Open events lost 0 —

So open-heavy work runs about 28% slower while a drive is mounted with taint on. That's why it's off by default and only active while a destination is mounted. A later improvement could use FAN_REPORT_FID to avoid a file descriptor per event.

Test plan

  • Taint store: newest first, per-process cap, reopen moves to the front, opens before the process started and outside the window don't count, pruning, process cap.
  • Decider: an encrypted archive after reading a sensitive file gets a correlated event (source name, hash) and the after-write action; a clean readable file is not correlated; with no sensitive source the usual unscannable event stays; taint off gives no correlation; a monitor policy records without touching the file.
  • The source opener refuses a replaced file (new inode) and a symlink. The process start time is parsed correctly with odd command names. Opens outside the root aren't recorded.
  • End to end as root (TestEnforcer_TaintEndToEnd, added to CI's must-run list): a shell reads a secret file and writes a binary blob to the tmpfs stick, giving a correlated event with plan.txt as the source. Another process writing the same blob gives no event.
  • golangci-lint: 0 issues (linux, darwin, windows). gosec: 0. Race tests, actionlint, make verify-cross and the changelog check pass.
  • Not tested on a real Ubuntu desktop with a real USB stick.

Refs #334

🤖 Generated with Claude Code

Neamul Haque and others added 2 commits November 2, 2026 17:46
process_taint (Linux, off by default): a file written to a monitored drive
that can't be judged on its own (unreadable, or an archive) is judged by
the files its writer read just before.

- A second fanotify group gets FAN_OPEN notifications for the mount holding
  the home folders, only while taint is on and a destination is watched; it
  is opened the first time it's needed. Its own reader means a flood of
  opens can only overflow its queue, never the destination events.
- Each open under /home costs a readlink and an fstat; the path and
  identity go into an in-memory store (32 per process, 4096 processes,
  taint_window_seconds). Nothing is scanned at open time.
- At a destination event that matched nothing and couldn't be judged on its
  own, up to 8 sources (newest first, opened after the process started, so
  a reused pid inherits nothing) are reopened with O_NOFOLLOW, checked to
  be the same file, and scanned through the cache. A match replaces the
  fail-mode events with correlated ones (correlation {method: taint,
  source_sha256, source_name}, verdict_source correlation), and the
  after-write action applies. A readable file that matched nothing is never
  judged by its writer's earlier reads.
- capabilities.copy_correlation.process_taint: supported on Linux. Health
  reports processes tracked and open events lost to overflow.
- Measured (privileged container, 100,000 opens while a drive is mounted):
  the workload took 1.05 s instead of 0.83 s (~28%), the agent used ~5 us
  of CPU per open, no events were lost.
- CI's root step must also run TestEnforcer_TaintEndToEnd.

Refs #334

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI (base), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7fe6dfed-4603-4627-8367-4968822fd3c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Windows can't rename a file that is open, so the test's destination file
was never there and the fake scanner got nil. Write it under its final
name, then open it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 2, 2026

Copy link
Copy Markdown

@neamul602
neamul602 merged commit 29497f6 into dev Nov 2, 2026
23 checks passed
@neamul602
neamul602 deleted the feat/334-process-taint branch November 2, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant