testsAndMisc/phone_focus_mode/config_curfew.sh
Krzysztof kuhy Rudnicki 7dc3c94160
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
config.sh: split 571 -> 250, with a fingerprint that actually works
The brief's prescribed safety net for this file DOES NOT WORK, and that is
the main finding here. It specifies asdict + json.dumps(default=str) +
sha256 against baseline 1624750c. Four policy fields are frozenset, which
json cannot encode, so default=str stringifies them in SET ITERATION ORDER
-- random per process. Three consecutive runs gave three different digests;
pinning PYTHONHASHSEED=0 gave one. The recipe can never match a recorded
baseline, so it would have flagged every split as a regression regardless
of correctness.

The repo already ships a deterministic serializer. Before trusting it I
checked it was not a weaker proxy: policy_to_json emits all 16 dataclass
fields plus 6 more, so it is strictly stronger than asdict. Baseline
83e05e82, unchanged through all six extractions.

Two audits ran BEFORE any extraction:

  - the loader regex-scans config.sh's TEXT ALONE, so the fifteen names it
    reads are pinned here. Moving one does not error -- it silently yields
    an empty set, and for WHITELIST that reads as "hide everything".
  - extractions are variable-scoped, never line-range. A line-range attempt
    cut through the WHITELIST string, leaving an unterminated quote; 15
    variables vanished from the environment WHILE THE FINGERPRINT STILL
    MATCHED, because the loader's regex tolerated the mangled file. That is
    why the environment diff exists alongside the hash.

The environment check earned its keep twice: it also caught STATE_DIR being
left behind the source line, which expanded every path against an empty
prefix (/status.json rather than the real path) with the policy hash green
throughout. 79 variables, byte-identical either side, and every parsed list
compared entry-by-entry: 29/16/46/1/1/2/15, all matching.

Rationale prose moves to docs/policy-lists.md with pointers left behind,
including the blocks inside the list strings -- the loader and the shell
both ignore '#' lines there.

All five siblings are in both deploy lists. deploy.sh still sources the
chain from the checkout, run_phone.sh --help exits 0, 379 shell assertions
pass across nine suites, and focus_policy's 137 pytest cases pass.

config.sh is at EXACTLY 250 with no headroom; 184 of those lines are
loader-pinned lists that cannot move. Over-cap count 18 -> 17.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 17:17:02 +02:00

51 lines
2.9 KiB
Bash
Executable File

#!/system/bin/sh
# shellcheck shell=ash
# config_curfew.sh — the night-curfew enforcer's settings: poll interval, log
# and state paths, the grayscale/DND/network feature switches, and the
# per-UID network chain.
#
# Sourced by config.sh where these definitions used to sit.
#
# The curfew WINDOW ($NIGHT_CURFEW_START/END/ENABLED) is deliberately NOT
# here: focus_policy's loader reads those out of config.sh's text. Only names
# the loader never reads may live in a sibling.
# --- Curfew enforcer (grayscale + DND + per-UID network allow-list) ---
# See curfew_enforcer.sh. Always-on like dns_enforcer, but only ACTS while the
# curfew window is open AND focus mode is ON. Re-applies every interval so a
# manual toggle in Settings snaps back ("hard to turn off"). NOTE: snap-back is
# the realistic lock; true impossibility would mean blocking the Settings app,
# which risks system instability, so we deliberately do not.
export CURFEW_ENFORCER_INTERVAL=5
export CURFEW_ENFORCER_LOG="$STATE_DIR/curfew_enforcer.log"
export CURFEW_ENFORCER_STATE="$STATE_DIR/curfew_applied"
# Grayscale: force the display to monochrome via the accessibility daltonizer.
export CURFEW_GRAYSCALE_ENABLED=1
# DND: force Do-Not-Disturb to alarms-only so notifications stop pulling you in
# while the morning alarm still rings.
export CURFEW_DND_ENABLED=1
# Per-UID internet allow-list. DEFAULT OFF: highest-risk layer, must be proven
# on-device (`focus_ctl.sh curfew-test-on`) before it is trusted to fire
# unattended at 23:00. When on, only $NIGHT_WHITELIST app UIDs (plus
# root/system/shell + DNS) get network; every other app is cut off. It is also
# largely redundant with the app-disable layer, so leaving it off is safe.
export CURFEW_NET_ENABLED=1
export CURFEW_NET_IPT_CHAIN="FOCUS_CURFEW_NET"
# Android's netd periodically reasserts the whole `filter` table via
# iptables-restore, which atomically erases our custom chain (proven on-device:
# the chain vanishes ~every 5s). The main enforcer tick (5s) rebuilds it but
# leaves up-to-5s leak windows. This watchdog re-pins the chain every
# CURFEW_NET_REASSERT_INTERVAL seconds *from a cached UID list* (no pm fork).
# A 1s interval was measured on-device to peg netd at ~30% CPU continuously
# (an `iptables -L` fork every second, all night), overheating the phone -
# throttled to 15s (matches HOSTS_CHECK_INTERVAL/LAUNCHER_CHECK_INTERVAL
# pacing) as a deliberate tradeoff: leak window grows from <=1s to <=15s.
# The proper fix is netd's own per-UID firewall (ndc/eBPF), tracked as a
# follow-up; this remains an interim stopgap either way.
export CURFEW_NET_REASSERT_INTERVAL=15
export CURFEW_NET_UID_CACHE="$STATE_DIR/curfew_net_uids.txt"
# Manual test toggle: `focus_ctl.sh curfew-test-on` writes this file to force
# curfew ACTIVE regardless of clock, so the whole stack can be validated during
# the day. `curfew-test-off` removes it.
export CURFEW_FORCE_FILE="$STATE_DIR/curfew_force_on"