Skip to content

Latest commit

 

History

History
227 lines (180 loc) · 10.8 KB

File metadata and controls

227 lines (180 loc) · 10.8 KB

Just ScreenTime Security & Anti-Cheat Invariants

Reporting a vulnerability

If you find a security issue, please open a GitHub issue (for non-sensitive matters) or contact the maintainer privately at taiman.jp@gmail.com for anything that should not be public. Reports are best-effort triaged; this is a small independent project.

Just ScreenTime is a passive, read-only monitoring tool. It observes the foreground window and idle time; it does not read memory from other processes, does not inject code, does not hook input, and does not load kernel drivers. This document codifies the invariants that keep it compatible with anti-cheat systems (Riot Vanguard, Easy Anti-Cheat, BattlEye, VAC) and with standard endpoint AV heuristics.

These invariants are non-negotiable. Any future change that would violate them must either be rejected or be gated behind an explicit, documented exception with the reason recorded here.

Scope. They govern the shipped binaries — everything inside the MSIX package (JustScreenTime.UI, JustScreenTime.Agent, JustScreenTime.Core). Scripts under scripts/ and tools/ are maintainer-side development aids that are never packaged, never installed, and never run on a user's machine; some of them (for example tools/Watch-ProcessStarts.ps1, a WMI process-start tracer used to diagnose console-window flashes) deliberately do things the app itself must not do.

0. Network and privacy boundary

The shipped 1.2 binaries must not contain an application feature that sends usage history, diary text, settings, diagnostic logs, or exports to an external service. They also must not add a server/listener, cloud sync, telemetry, analytics, advertising, or remote crash-reporting SDK.

Local bounded error logging is allowed: ErrorLog writes exception details to per-user LocalApplicationData, rotates each log at approximately 512 KiB, keeps one previous generation, and never uploads it automatically.

The absence of internetClient in the MSIX manifest is useful package metadata, but it is not a security boundary for a runFullTrust desktop process. Therefore, privacy claims must be verified against shipped code and dependencies, not inferred from capability declarations. Any future network feature requires explicit user-facing consent, an updated privacy policy, and a review of the Store listing before release.

1. Process access — minimal privilege only

When opening a handle to another process, Just ScreenTime must use only PROCESS_QUERY_LIMITED_INFORMATION (0x1000).

Forbidden access masks:

  • PROCESS_QUERY_INFORMATION (0x0400) — the older, more privileged form
  • PROCESS_VM_READ (0x0010)
  • PROCESS_VM_WRITE (0x0020)
  • PROCESS_VM_OPERATION (0x0008)
  • PROCESS_CREATE_THREAD (0x0002)
  • PROCESS_DUP_HANDLE (0x0040)
  • PROCESS_SET_INFORMATION (0x0200)
  • PROCESS_SUSPEND_RESUME (0x0800)
  • PROCESS_ALL_ACCESS
  • SYNCHRONIZE

PROCESS_QUERY_LIMITED_INFORMATION is the Microsoft-recommended form for monitoring tools; it is designed to be permitted against protected processes and is generally tolerated by user-mode anti-cheat components.

Documented exception — own Agent child process only: The UI may call Process.WaitForExit / Process.Kill against JustScreenTime.Agent.exe that this package spawned, which requires SYNCHRONIZE and PROCESS_TERMINATE on that specific process. This is allowed solely for graceful shutdown / restart of our own tracker worker. It must never be used against third-party processes, games, or any PID obtained from foreground probing.

2. Forbidden APIs

Never call, P/Invoke, or dynamically load any of these:

Memory / code injection

  • ReadProcessMemory, NtReadVirtualMemory
  • WriteProcessMemory, NtWriteVirtualMemory
  • VirtualAllocEx, VirtualProtectEx
  • CreateRemoteThread, NtCreateThreadEx, RtlCreateUserThread
  • QueueUserAPC against another process
  • SetThreadContext against another process

Hooks / input interception

  • SetWindowsHookEx (any WH_* constant)
  • SetWinEventHook with WINEVENT_INCONTEXT flags
  • RegisterRawInputDevices with RIDEV_INPUTSINK
  • Low-level keyboard/mouse hooks of any kind

GetLastInputInfo is allowed — it reads a system counter, does not hook, and is the Microsoft-documented way to measure user idle time.

Debugger APIs

  • DebugActiveProcess
  • WaitForDebugEvent
  • Any adjustment of SeDebugPrivilege

Process enumeration

  • NtQuerySystemInformation with SystemProcessInformation
  • EnumProcesses / EnumProcessModules
  • CreateToolhelp32Snapshot with TH32CS_SNAPPROCESS or TH32CS_SNAPMODULE

The tracking path — everything that decides what gets recorded — only ever looks at the foreground PID returned by GetWindowThreadProcessId. It must never walk the global process list, and the Agent (the process that does the tracking) contains no enumeration of any kind.

Documented exception — the UI's Agent supervisor. AgentProcessManager (UI process only) calls System.Diagnostics.Process.GetProcessesByName, which internally uses NtQuerySystemInformation(SystemProcessInformation). It runs when the UI starts, on the 30-second watchdog tick, and on stop/restart. It is needed to answer one question — "is our own Agent alive in this logon session?" — because a tray-resident UI must notice a dead Agent without waiting for the next logon. Constraints that keep it inside the spirit of this section:

  • it matches only JustScreenTime.Agent and only in the current session ID; results for other processes are discarded without any property being read
  • it never opens a process handle, reads memory, or touches MainModule
  • it never runs in the Agent, and never on the path that decides what to record
  • Process.Kill() is used without entireProcessTree, which would build a machine-wide parent/child map

Kernel / driver

  • No NtLoadDriver, no CreateService with SERVICE_KERNEL_DRIVER, no ZwLoadDriver, no .sys files shipped or installed.

Other flagged patterns

  • LoadLibrary / LdrLoadDll against DLLs inside another process
  • Anything that looks like anti-debug / anti-VM detection (IsDebuggerPresent loops, CheckRemoteDebuggerPresent, CPUID VM detection) — anti-cheats flag these even when the caller is benign

UWP / Store app resolution (1.2.0.0)

Every UWP app's foreground window belongs to ApplicationFrameHost, so attributing time to the actual app requires looking one level in: EnumChildWindows on the frame window plus GetClassName to find the Windows.UI.Core.CoreWindow child, then GetWindowThreadProcessId on that child. These are window-level APIs — no process handle, no memory access, no global process list — and the resolved PID goes through the same PROCESS_QUERY_LIMITED_INFORMATION path as any other. Enumeration is scoped to the children of one window we are already allowed to see.

2a. Window enumeration (Phase 3.5)

Background-session tracking requires listing open top-level windows to decide which apps are "still open but not in the foreground". The following user32 APIs are allowed for this purpose:

  • EnumWindows — iterates top-level windows by HWND only.
  • IsWindowVisible / IsIconic — query window state (shown / minimized).
  • GetWindowThreadProcessId — returns the owning PID (already permitted in §2).

None of these read process memory or open handles to target processes. Task Manager, accessibility tools (screen readers, magnifiers), and Alt-Tab alternatives call them continuously; anti-cheat systems do not flag them.

Constraints (see JustScreenTime.Core/Win32/BackgroundWindowTracker.cs):

  • Enumeration runs at most once per ProbeEveryNTicks agent ticks (currently every 5 seconds), not every tick.
  • PID → exe-path resolution still routes through ProcessInfoCache, so §3's once-per-60-seconds OpenProcess budget is unchanged by window scanning.
  • The agent's own exe (any path containing "JustScreenTime") is filtered from the result set before it reaches the tracker, so Just ScreenTime cannot record itself.

3. Rate limiting

Even with permitted APIs, repeated probing of the same protected process looks like monitoring malware to heuristic scanners. Just ScreenTime enforces:

  • At most one OpenProcess per PID per 60 seconds for successful lookups, via ProcessInfoCache (see JustScreenTime.Core/Win32/ProcessInfoCache.cs).
  • At most one OpenProcess per PID per 30 seconds for failed lookups (negative cache). A PROCESS_QUERY_LIMITED_INFORMATION denial from OpenProcess is treated as a permanent-enough signal that the process is protected; we stop asking.
  • The polling tick itself (currently 1 second) only calls GetForegroundWindow + GetWindowThreadProcessId + GetLastInputInfo, which do not open handles to any target process.

4. Process privilege

The agent must not run with elevated privileges it does not need.

  • When running interactively (Phase 1 / current): runs as the logged-in user.
  • When running as a Windows Service (Phase 4, planned): target is LocalService, not LocalSystem. This is a Phase 4 decision point and must be revisited before MSIX packaging.
  • SeDebugPrivilege must never be enabled on the agent's token.

5. Distribution / signing

  • Unsigned binaries doing process introspection are flagged more aggressively by SmartScreen and Defender. Phase 4 (Store submission) is the point at which an Authenticode signature becomes available via MSIX.
  • Until then, users installing from source should expect SmartScreen warnings. This is explicitly not worked around by any "bypass" technique.

6. User-facing exclusion list (Phase 3)

The Phase 3 settings page will add an explicit exclusion list: processes the user names are tracked only by their window title (GetWindowText), with no OpenProcess call at all. This is the last-resort escape hatch for users who want to run Just ScreenTime alongside any anti-cheat whose behavior is unknown.

7. Review checklist

Before merging any change under JustScreenTime.Core/Win32/ or anything that adds new P/Invoke signatures, verify:

  • No new access masks beyond PROCESS_QUERY_LIMITED_INFORMATION — see Section 1 for the exhaustive list, which has no exceptions.
  • No APIs from the forbidden list in Section 2, except the one documented exception recorded there (the UI-only Agent supervisor). New exceptions must be written into Section 2 with their scope and constraints before the code merges.
  • No new code path opens a handle to the same PID more than once per minute without going through ProcessInfoCache.
  • No new dependency pulls in System.Diagnostics.Process.MainModule, which internally uses PROCESS_QUERY_INFORMATION on older frameworks.
  • Class-level documentation in WinApi.cs is still accurate.