diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36e6fec..62119eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,13 +63,14 @@ jobs: - run: cargo test --workspace release-invariants: - # #176: turn the #161-class privacy invariant into a permanent gate. CI's + # #176/#177: turn the #161-class privacy invariant into a permanent gate. CI's # other jobs only compile/test the DEBUG profile, so a dropped - # `#[cfg(debug_assertions)]` guard (e.g. a bad merge on the #64 bleed dump or a - # #108 dev flag) is invisible to them and only surfaces at tag time. This job - # compiles livecap-app in RELEASE and asserts the debug-only env-escape NAMES - # are absent from the binary — they exist only inside `#[cfg(debug_assertions)]` - # items, so a release build compiles them out entirely (#146/#161). + # `#[cfg(debug_assertions)]` guard (e.g. a bad merge on the #64 bleed dump, a + # #108 dev flag, or the #177 model-base-url knob) is invisible to them and only + # surfaces at tag time. This job compiles livecap-app in RELEASE and asserts the + # debug-only env-escape NAMES are absent from the binary — they exist only + # inside `#[cfg(debug_assertions)]` items, so a release build compiles them out + # entirely (#146/#161/#177). runs-on: macos-14 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -98,7 +99,7 @@ jobs: # These env vars are read ONLY inside #[cfg(debug_assertions)] items, so a # release build must contain none of their names. A hit means a guard was # dropped and the shipped binary regained a covert env path (#161-class). - FORBIDDEN="LIVECAP_BLEED_DUMP_DIR LIVECAP_CAPTURE_VISIBLE LIVECAP_AUTOSTART LIVECAP_UI_PROBE" + FORBIDDEN="LIVECAP_BLEED_DUMP_DIR LIVECAP_CAPTURE_VISIBLE LIVECAP_AUTOSTART LIVECAP_UI_PROBE LIVECAP_MODEL_BASE_URL" fail=0 for name in $FORBIDDEN; do if strings "$BIN" | grep -q "$name"; then diff --git a/crates/livecap-core/src/model.rs b/crates/livecap-core/src/model.rs index c0dbab3..cb65f1d 100644 --- a/crates/livecap-core/src/model.rs +++ b/crates/livecap-core/src/model.rs @@ -33,13 +33,29 @@ const HF_REPO: &str = "https://huggingface.co/ggerganov/whisper.cpp"; /// source — [`pointer_url`] is pinned to the official [`HF_REPO`] — so an /// overridden host cannot serve a fake LFS pointer alongside a matching fake /// blob; any blob that does not match the OFFICIAL digest fails verification. +// Release builds never read this env var (#177), so the const is unused there — +// which is the point: an unused const emits no string, so the release binary +// carries no `LIVECAP_MODEL_BASE_URL` symbol (asserted by the release-invariants +// CI gate). The `allow(dead_code)` only silences the release-profile warning. +#[cfg_attr(not(debug_assertions), allow(dead_code))] const MODEL_BASE_URL_ENV: &str = "LIVECAP_MODEL_BASE_URL"; -/// The effective BLOB download base URL (env override or the official repo). +/// The effective BLOB download base URL. Debug builds honor the +/// [`MODEL_BASE_URL_ENV`] dev-run knob (#110 download-failure fallback); RELEASE +/// builds ALWAYS use the official [`HF_REPO`] — the env read is compiled out +/// entirely (#177, mirroring #146/#161), so a shipped binary exposes no such knob +/// and its model-download host can't be redirected. The pure +/// [`base_url_or_default`] core stays testable in both profiles. +#[cfg(debug_assertions)] fn blob_base_url() -> String { base_url_or_default(std::env::var(MODEL_BASE_URL_ENV).ok()) } +#[cfg(not(debug_assertions))] +fn blob_base_url() -> String { + base_url_or_default(None) +} + /// Pure core of [`blob_base_url`]: `None`/blank → the official repo. Trailing /// slashes are trimmed so the joined download URLs stay well-formed. fn base_url_or_default(overridden: Option) -> String {