Skip to content

Host the Hardened Secret and Env Readers in the Hub and Carry Them as a Synced Copy #3505

Closed

Description

@ptr727

Summary

Fleet repositories that read config, secret, and env files each harden that read their own way, or not at all. HomeAutomation-Config's scripts/paths.sh is the hardened implementation today. require_secret, secret_verdict, read_env_value, and the path checks distinguish a missing file from a dangling symlink or an unreadable ancestor. They refuse symlinks, FIFOs, devices, group or other access, a wrong owner, and CRLF. read_env_value reads one value through a child bash, so nothing in the file is exported. ptr727/Blog's deploy/make-release.sh does the same job with an existence check and set -a; . "$ENV_FILE", with no mode, owner, symlink, or CRLF check. The hub has no equivalent library, and spec/files.json carries no code except the .github/skills tree.

Decision

The maintainer chose a synced copy refreshed at resync over a runtime pull from the hub and over a git submodule.

  • Not a runtime pull. This code runs on maintainer hosts under systemd timers (backup, healthcheck, crash-loop breaker) from an installed copy, sometimes as root. A hub fetch at run time makes a GitHub outage or a bad main commit stop a backup, and runs code the consuming repository never reviewed against its secrets. The fetch-latest principle stays right for gates and hooks, where "the tool did not run" is an acceptable answer and CI is the backstop.
  • Not a submodule. Every worktree would need a submodule init, the whole hub would come along, and carry.py cannot audit inside one.

Design

  1. The hub hosts the generic library, for example lib/sh/secrets.sh, with its tests run in hub CI. It takes the generic parts of HomeAutomation-Config's scripts/paths.sh. A repository-specific part stays in its repository, such as push_interlock, and the root-owned docker.env owner exemption becomes a parameter. Shell comes first, since no fleet repository has a Python env reader and the hub's host setup avoids needing Python.
  2. It is carried byte-for-byte through spec/files.json to a fixed consumer path, for example lib/fleet/, with its tests, so the consumer's own CI runs them against its copy. A repository extends it in its own files that source it, never by editing it.
  3. A stale copy is visible. carry.py already tells a stale copy from a modified one. A consumer's CI reports a stale library copy as a warning rather than a failure, and the audit and resync apply the refresh. A failure would break every downstream pull request on each hub fix.
  4. GOVERNANCE.md "Hub-Hosted Tooling" gains the rule. Code a repository's own runtime executes is carried, while machinery that measures a repository is still reached. The section today calls carrying machinery a copy whose only future is to go stale, and a runtime library is the case where offline operation and review in the consuming repository outweigh that. The same rule gives the existing byte-identical hub_fetch_run.py copy a declared home.
  5. Rollout. HomeAutomation-Config, today's source, moves onto the carried copy first. Its installer already ships scripts/* to the host, so the carried path joins that list. ptr727/Blog's make-release.sh then uses require_secret and read_env_value, which pairs with Rename the Host Env Files to the Fleet's Underscore-Joined Form Blog#550's env file rename. Each repository's adoption is its own issue in that repository.

Done When

The hub hosts the library with tests, spec/files.json carries it, a stale copy warns in a consumer's CI, "Hub-Hosted Tooling" states the runtime-library rule, and HomeAutomation-Config runs on the carried copy.

🤖 Generated with Claude Code

Activity

  1. ptr727 commented on Oct 26, 2026

    @ptr727
    OwnerAuthor

    Design settled with the maintainer before the first edit. Two premises in the design above did not hold against the source.

    • carry.py does not tell a stale copy from a modified one. Its tree comparison reports any byte difference as modified, so design step 3 needs a mechanism of its own.
    • HomeAutomation-Config's scripts/paths.sh has no owner check, so there is no docker.env owner exemption to turn into a parameter. The one parameter that exists is the private|shared access mode.

    The maintainer's answers:

    1. Selector. A new project type, host-secrets, which a repository declares in registry/repos.json to carry the library.
    2. Tests. Hub CI only. A consumer carries the library alone, and the stale-copy check proves its copy is byte-identical to a tested hub version, so validate-task.yml does not change.
    3. Stale-copy signal. A repo_gate.py check compares each carried tree with the hub tree the pinned action already holds. It reports a difference as a warning note that never changes the exit code, so the result can be reproduced at the pin, and the audit and resync apply the refresh.
    4. Scope. The secret and env family moves to the hub: unresolved_link_reason, secret_state through require_secret, and read_env_value. The installer path helpers, the push interlock, and write_state_file stay in HomeAutomation-Config. The owner check is out of scope.
  2. ptr727 commented on Oct 26, 2026

    @ptr727
    OwnerAuthor

    The hub side landed in #3509 and is in promotion #3512, which closes this issue when it merges. The library, its tests, the spec/files.json tree, the stale-copy warning (the carried-trees repository gate check), and the "Hub-Hosted Tooling" rule are all in. The last Done When item, HomeAutomation-Config running on the carried copy, moves to ptr727/HomeAutomation-Config#1435 per design step 5. Blog's adoption is ptr727/Blog#553. Both wait on the release that carries #3512.

  3. added a commit that references this issue on Oct 27, 2026
    7d0e5bf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestscriptA defect in hub tooling

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions