File size: 3,831 Bytes
2aa8b3a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""Stop the dependencies phoning home, before they get the chance.

**What was happening.** Constructing a `gradio.Blocks` reaches
`api.gradio.app` and `huggingface.co`. Constructing a `gradio_client.Client`
reaches `huggingface.co` twice, once for a registry lookup and once for
telemetry carrying the URL of the server the operator typed. Both happen on
import or construction, before anything is served and before any pairing
succeeds, and the second one is worse than it sounds: on a self-hosted, tunnel
or private-network deployment, that URL is the thing the operator was keeping
private.

`distinct_agent/transport.py` already passed `analytics_enabled=False` to the
client. It does nothing. `gradio_client` reads it as::

    analytics_enabled or os.getenv("GRADIO_ANALYTICS_ENABLED", "True") == "True"

and `False or True` is `True`, so the keyword cannot turn anything off. The
environment variable is the only control that works, and the registry lookup
inside `huggingface_hub` answers to neither: only `HF_HUB_OFFLINE` stops it.

**Why this is set here rather than documented.** A project whose whole claim is
that prompts stay on machines their operator chose does not get to ship a
default that contacts two third parties on start-up and print a note about it
in a README. Deployment advice is not a control. This is a control.

**Set, not forced.** Each variable is left alone if the operator has already
set it, including to a value that turns telemetry back on. Somebody who
deliberately wants the upstream analytics can have them; nobody gets them by
default and by accident.

`HF_HUB_OFFLINE` does not affect model weights. Nothing in this repository
imports `huggingface_hub`: `distinct_agent/weights.py` downloads with
`urllib.request` from a pinned URL and verifies a SHA-256 before the file is
put into place. The variable only silences the library gradio brings with it.

**What this cannot do.** It works because it runs before `gradio` or
`gradio_client` is first imported, which is why the calls sit at the top of
`distinct_server/__init__.py` and `distinct_agent/__init__.py`: importing
either package runs its `__init__` first. Code that imports `gradio` before it
imports anything of ours is outside this, and a test in
`tests/test_redteam_regressions.py` pins the ordering so that a later import
tidy-up cannot quietly undo it.
"""

from __future__ import annotations

import os
from collections.abc import MutableMapping
from typing import Optional

#: The variable, and the value that means "do not". Each is the spelling the
#: library in question actually reads; they are not interchangeable and they
#: are not all booleans, which is why they are written out rather than
#: generated.
TELEMETRY_DEFAULTS: dict[str, str] = {
    # gradio and gradio_client both read this, and both compare it to the
    # string "True", so anything else at all disables them.
    "GRADIO_ANALYTICS_ENABLED": "False",
    # huggingface_hub's own usage reporting.
    "HF_HUB_DISABLE_TELEMETRY": "1",
    # The registry fetch that runs inside build_hf_headers on every client
    # construction. Telemetry flags do not stop it; this does.
    "HF_HUB_OFFLINE": "1",
}


def silence_third_party_telemetry(
    environ: Optional[MutableMapping[str, str]] = None,
) -> tuple[str, ...]:
    """Default the telemetry variables to off. Returns the names it set.

    Returning the names rather than nothing so a caller can say what it did,
    and so a test can tell "already off" from "turned off here" without
    reading the environment twice.
    """

    target = os.environ if environ is None else environ
    changed: list[str] = []
    for name, value in TELEMETRY_DEFAULTS.items():
        if target.get(name) is None:
            target[name] = value
            changed.append(name)
    return tuple(changed)