| summary | Health data safety model, OAuth token handling, local files, and exports. | |||
|---|---|---|---|---|
| read_when |
|
gohealthcli stores sensitive personal health data locally. Treat the archive
and token material as private.
The First Release is read-only:
- No health writes.
- No deletes.
- No webhook receiver.
- No cloud service.
- No automatic sharing or background upload.
Google Health API access needs a Google Cloud project, OAuth client, configured scopes, and test-user setup while unverified.
connect should request scopes for the configured Data Types. sync must not
start an unexpected browser consent flow; if a requested Data Type needs missing
scopes, fail with a clear re-connect instruction.
Token material must not print in normal command output. doctor may report
presence, expiry shape, and scopes, but not token values. Default doctor
should stay local; doctor --online is the explicit path for token refresh and
provider reachability checks.
Structured authentication remediation is output-only metadata. JSON may expose
an ordered remediation array and plain output the matching zero-based
remediation.N fields; human output is unchanged. Recovery commands come from
the Failure Reporter's fixed catalog, with the sole parameterized form limited
to sorted connect --add-scopes keywords already present in the public scope
catalog. Error messages, tokens, authorization codes, identities, paths,
Provider payloads, SQL, health data, and user input are never converted into
actions. Building or rendering actions performs no Provider I/O and cannot
start an OAuth flow.
Sync and Health Archive recoveries use the same output-only boundary. The only
Sync-specific action is the fixed
gohealthcli sync --from YYYY-MM-DD Initial Backfill template; it never copies
the operator's Data Types, dates, paths, or other arguments. Fan-out actions
remain on the affected child. Missing archives may suggest diagnosis followed
by initialisation, but canceled, corrupt, invalid-query, and unknown failures
remain actionless. Classification uses typed causes only and does not read or
mutate the Provider, OAuth state, Sync Cursor, audit trail, or archive.
OAuth token material should live in a Credential Store:
- macOS: Keychain.
- Windows: Windows Credential Manager.
- Linux: Secret Service/libsecret when available.
- File fallback: permission-restricted local file, explicit opt-in for development or unsupported environments.
1Password may be used as a Secret Provider for bootstrap material such as a
Google OAuth client secret. init stores exact Secret Provider references;
connect consumes resolved OAuth client config and should not search 1Password.
1Password should not be the default runtime token backend.
Environment variables are for development only.
Expired or unrefreshable tokens are Connection health problems, not Health
Archive corruption. connect may re-authorize the same Google Identity and keep
using the existing archive. If re-authorization returns a different Google
Identity, require an explicit new archive or a future multi-identity decision.
Headless authorization uses the same Desktop-client loopback and PKCE S256
flow as interactive connect. connect --headless-start stores its verifier,
state, exact redirect, expiry, and config/archive/client/scope/identity binding
only in the configured Credential Store for ten minutes. connect --complete
accepts one complete redirected URL on stdin only. It validates the exact
origin and path, binding, expiry, state, and full requested scope grant before
identity validation or token storage. A claim is atomic across processes, so
only one concurrent completion can exchange the code. Treat authorization and
redirected URLs as sensitive transfer material even though neither is stored
in the Health Archive.
Default paths should follow the gobankcli pattern:
- config:
~/.config/gohealthcli/config.toml - archive:
~/.local/share/gohealthcli/gohealthcli.sqlite - credential references or file fallback tokens:
~/.config/gohealthcli/
Config and token files should be created with owner-only permissions.
The default archive path does not include Google Identity. The archive stores
that identity internally; multiple identities require explicit future design or
explicit alternate --db paths.
The defaults are anchored to the user's home directory. When HOME is unset and
no absolute XDG_CONFIG_HOME/XDG_DATA_HOME override is set (a relative XDG_*
value is ignored per the XDG Base Directory spec), the binary cannot anchor the
default and fails loudly — "cannot determine home directory; set HOME or pass
--config/--db explicitly" — instead of silently writing personal health data to
a current-working-directory-relative path. Passing --config/--db explicitly
is unaffected.
Backup work is explicit. backup init may create a Git checkout and push its
recovery README to the configured remote. backup push exports only the current
local Health Archive, writes age-encrypted JSONL gzip shards plus the documented
cleartext manifest, commits, and pushes unless --no-push is set. It does not
sync, refresh Identity Snapshots, read the Credential Store, or contact Google
Health.
Recipient changes are explicit configuration changes. backup init canonicalizes
the complete desired recipient set, and the next backup push re-encrypts even
unchanged Snapshot shards before committing them. Later unchanged pushes reuse
the authenticated ciphertext. Removing a recipient affects only the current and
future backup tree; compatible ciphertext remains recoverable from earlier Git
history by identities that could decrypt it then.
backup pull refreshes the configured Git checkout, decrypts and hashes every
manifest-owned shard in memory, then validates the complete Health Archive
Snapshot before creating a fresh target archive. It never overwrites an existing
archive. Attachment rows and payloads restore together through the Attachment
Store containment and owner-only permission rules; any wrong identity, corrupt
or missing shard, invalid JSONL, unsupported manifest, or broken Snapshot
reference fails before a target is reported as restored.
The default backup.json and backup-age-identity.txt live beside the main
config under the XDG-style gohealthcli config directory and are owner-only.
They must never be committed. Private Credential Store token material, OAuth
client secrets, and Secret Provider contents are excluded from backup. A
restored Health Archive may require connect before Provider commands work.
The Git repository remains sensitive metadata. Its cleartext manifest may
reveal export time, public recipients, logical table names, row counts, shard
paths, encrypted sizes, plaintext hashes, backup cadence, and changed shards.
Private health payloads and Attachment bytes must be encrypted before Git sees
them. Shard plaintext exists only in process memory before fixed-metadata gzip
compression and age encryption; Git receives only ciphertext. Loss of every
matching age identity makes the backup unrecoverable;
compromise of an identity can expose compatible shards retained in Git history.
Age authenticates ciphertext integrity for a recipient, not the identity of the
Git publisher. backup pull therefore trusts write access to the configured
owner-controlled remote; protect that remote and review unexpected history or
collaborator changes before restoring.
backup status reads only the backup config, checkout, and cleartext manifest.
It does not require the private identity, decrypt shards, open the live Health
Archive, contact the Provider, or mutate Git state.
Exports can reveal sensitive health history. Commands should require explicit
output paths or explicit --stdout. Avoid silent background exports.
- Remote secret storage.
- Browser scraping.
- Android device scraping.
- Medical interpretation.