Skip to content

docs: access model, geodata lifecycle and one SSRF policy (C3) - #38

Merged
Zakkaus merged 7 commits into
daeuniverse:honkfrom
Zakkaus:fix/contract-access-geodata
Sep 28, 2026
Merged

Zakkaus merged 7 commits into
daeuniverse:honkfrom
Zakkaus:fix/contract-access-geodata

Conversation

@Zakkaus

@Zakkaus Zakkaus commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Contract PR C3 of the API revision plan (access, geodata, SSRF). Plan decisions Q1 and Q8, plus the F12 and F43 paragraphs. Every changed rule was checked against honk Glassyiris/honk@feat/native-api and HB #59 (ae4453ce), and dae b59e375. dae has no native HTTP adapter or geodata downloader yet, so "dae" below means what a Go adapter needs to implement the text.

F1: one visibility model (Q1)

  • Change: api-config.md#Visibility is the only visibility rule. Every admitted caller sees the same data whatever its permission, auth mode or detail tier. Listener secrets are defined by role (each listener's deployment secret, the administrator password, session tokens; honk's are native_api.secret and clash_api.secret). An engine masks the fields that carry them and the deployment secret values it holds; it need not keep a recoverable password or old tokens to find copies. Proxy and subscription credentials in node definitions, share links and provider URLs are returned as written. Parser and log output is sanitised, not forwarded. The leftover "never returned" and path-redaction rules are removed (api/providers.yaml, api/nodes-groups.yaml, api/config.yaml, api/discovery.yaml config.content, providers.md, node-latency.md, honk-mapping.md Providers row); errors.md links to the table. The remaining copies now refer to the table too: rule and DNS-rule source.file is returned as written and source is null only when unavailable (rules.md, dns-rules.md, api/rules.yaml); flow input fields follow the table and only the packet-body prohibition stays (flows.md); rule expressions are "display", not "sanitized display". Examples match: the provider list no longer says "without credentials", a created provider returns the URL it was created with, and rule/DNS examples show plain source paths.
  • HC: stop redacting geodata settings URLs for callers without a credential (native_api/settings.rs with_geodata). The write rule (403 when not credentialed) stays.
  • dae: mask the credential fields and the deployment secret before storing or emitting text; return everything else unchanged.

F24: auth modes and sessions

  • Change: auth.md and api/auth.yaml order the checks: Authorization header, peer (setup only), account state, body. A header without a live session gets 401. With a live session, setup gets 409 setup_already_completed, and login still validates the body and opens another session only with correct credentials. api-config.md keeps session access and lifetime only. honk's 32-session limit and 12-hour lifetime move to a non-normative honk-mapping.md section; they are examples, not requirements. Version and capabilities also require a bearer in password mode (api/discovery.yaml).
  • HC: none. This is honk's order (security.rs boundary authenticates the header, then auth/request.rs checks peer, setup_required(), and the credentials).
  • dae: a process-owned auth service with a session map, expiry and size cap, separate from the traffic session manager; middleware authenticates the header before the handler runs.

F16: geodata lifecycle and bounds (Q8)

  • Change: source: db becomes override. resources.geodata.lifecycle advertises file_values (start or activation) and overrides_persist; honk's policy (start, true) is the documented default. geodata.md has a normative transition table: file field removed, same-value PATCH (stays override), omitted field, null, activation, restart without persistence. Patching one asset overrides only that list. When configurable_sources is true, max_urls, interval_hours {min,default,max}, checksum and lifecycle are required, and min ≤ default ≤ max. JSON Schema checks presence and minimum: 1 only; the ordering is a semantic constraint that tools/contract.mjs validateCapabilities checks, and the test runs it directly and shows the schema alone accepts inverted bounds. checksum is required whenever can_update is true; null means unverified, and pinned (a digest the backend holds) is allowed. sha256sum appends .sha256sum to the path and keeps the query; a 404 means no checksum (accepted unverified), any other failure fails the URL. The schedule is anchored at the end of the last attempt; before this process finishes one, at process start or the latest auto_update change, whichever is later. next_check_at is set from startup while automatic updates are on; only last_checked_at and last_updated_at start null. The random delay is part of every wait, retries included. runtime-status.md no longer fixes a geodata lifetime; it points to the advertised lifecycle and the transition table. geodata.md:41 and api/geodata.yaml:11 wording fixed. honk-mapping.md describes honk as it is today (db, both lists stored on a one-asset patch, bounds enforced but not advertised).
  • HC: emit max_urls: 4, interval_hours {6,168,24}, checksum: "sha256sum" and lifecycle {file_values: start, overrides_persist: true} (types.rs, geodata.rs capability). Serialize Source::Db as override (geodata/sources.rs:151). In Sources::apply, store only the patched URL list instead of freezing the sibling.
  • dae: a process-owned geodata manager with a sidecar state file (as common/subscription already persists files), its own bounds, and validation before mutation. A bounded, no-redirect downloader; pinned fits scripts/fetch-geo-data.sh's digests.

F12: display URLs

  • Change: a deterministic display form equal to honk after HB #59 (geodata::redact, looks_like_token): drop the query; redact the segment after a listed credential name, segments containing : or =, UUIDs, 32 hex digits, URL-safe segments of 16+ characters with upper, lower and digit, and URL-safe segments of 32+ characters without - that are not lower-case hex. Null when the URL does not parse, including a URL with userinfo or a fragment (HB's parse_geodata_url rejects both).
  • HB #59: implements this rule; current feat/native-api still keeps the query in source_redacted (geodata.rs:229).
  • dae: net/url parse, reject userinfo and a non-empty Fragment, clear RawQuery, apply the listed segment tests.

F43 and F11: one SSRF section

  • Change: api-config.md#Outbound-requests is the only copy. The restriction applies to check-execution requests; authorised writes (configuration sources, group check_url PATCH, geodata source PATCH) may change destinations, and the allowlists are deployment-owned. HTTP URL rules are separate from TCP/DNS host and port targets. Ports follow the destination kind: HTTP(S) takes the scheme default or an allowlisted port, TCP connect any nonzero configured node port, DNS 53 or an allowlisted port. A geodata URL equal, as the exact string, to the one the configuration file names for that asset is exempt from the address and port rules, checksum request included. Checks run after final route selection and before each dial and retry. When the backend resolves the name, direct or through a node, it validates and pins the address; when a node resolves it, only literals are checked; literals are always checked. Host and SNI keep the URL's name. The group health-check exemption covers both address and port rules, including direct members. groups.md, geodata.md and check-nodes.md link to it.
  • HC: none. honk's geodata.rs download checks ports, resolves and checks on direct, and checks literals on proxied routes; fetch skips the policy for the file URL and its checksum (geodata.rs:478,498-504, config/coordinator/geodata.rs:156-163); probes.rs:321-332 has the per-kind ports; its group health checker applies no destination policy.
  • dae: enforce in control/dial.go after route selection; dialer.go already dials a locally selected IP, which is pinned after the check. The geodata exemption is a string comparison with the configured file URL.

Scope notes

Checks

npx -y yarn@1.22.22 check:contract: 74/74 pass (bundle regenerated and committed). The drift ledger records the file-URL exemption, per-kind ports and schedule anchor as matching honk, and the fragment rule under HB #59.

Every admitted caller is an administrator. api-config gains an auth-modes
table (token, secretless loopback, password) with the session lifetime and
the live-session setup and login cases, and one visibility table that the
other pages link to. The observe-only redaction, private-path and local
file path rules that contradicted it are removed.
Rename source db to override and state the effective value, null reset and
override lifetime across restart and activation without honk's storage
model. The URL count, interval bounds and checksum method are advertised in
resources.geodata instead of fixed in the schema; honk's reconciliation and
timing constants move to the honk notes. Both display URLs share one display
form, and settings URLs are returned as written to every admitted caller.
Groups, node checks and geodata each carried their own copy of the SSRF
rules, and the geodata copy required a resolved-address check that a
download routed through a node cannot make. api-config now holds one
policy with the direct, routed and member-dialled cases, and the three
pages link to it.
@Zakkaus
Zakkaus merged commit 990663f into daeuniverse:honk Sep 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant