Repository navigation
feat(judge): ask the host for the judge credential on first use - #64
Conversation
JudgeConfig.credential was required at init, so a host whose credential sits in this module's own keyring could not supply it: the keyring is readable only once the module is admitted, and reading it during load deadlocks. openhuman therefore runs with the judge off. The credential is now optional. Without it, the module asks the init-bound trusted host through the confidential JudgeCredentialHost2.Credential callback on the first judgement, never during load, within the judge's timeout. The client is reused for five minutes; an endpoint rejection (401/403) drops it at once and the next ask says rejected; a reinit drops it too. No credential or a failed ask sends that request to a person, as an unavailable judge does today. A configured credential behaves exactly as before.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Tiny Sweeper reviewReviewed head: Verdict: Ready for maintainer review · Priority: none Diagnostics and supporting detailsFull summaryPull request purpose (title): feat(judge): ask the host for the judge credential on first use. The summary explains that `JudgeConfig.credential` was required at init, so a host whose credential lives in this module's own keyring could not supply it (the keyring is readable only once the module is admitted, and reading it during load deadlocks), leaving the judge off and every judged call going to a person. The credential is now optional; without it the module asks the init-bound trusted host through a new confidential `JudgeCredentialHost2.Credential` callback on the first judgement (never during load, bounded by the judge's `timeout_ms`), reuses the client for five minutes, drops it on a 401/403 with the next ask flagged `rejected`, drops it on reinit, and sends the request to a person when no credential or a failed ask occurs. A configured credential behaves as before. The latest-push delta is unavailable, so no push-delta description is given. The description lane notes a behavioural gap: the reinitialization path can never acquire a host credential, so a running module cannot move from a config-supplied credential to a host-served one. Review snapshot
Completeness: Complete What changed`JudgeConfig.credential` becomes `Option<Secret>`, omitted on the wire when absent, with new `JudgeCredentialReason2`, `JudgeCredentialRequest2`, and `JudgeCredentialResponse2` contract types and new `callback_names2::JUDGE_CREDENTIAL_HOST`/`CREDENTIAL` constants. `tinysecurity-auto` gains a `host` module with `CredentialSource`, `HOST_CREDENTIAL_TTL_MS`, a `HostClient` that asks the host lazily and caches for five minutes, `Engine::with_credential_source`, `SdkClient::with_credential`, and `Error::Rejected` mapped from SDK authentication failures. `Engine::validate_config` validates the endpoint and timeout with a placeholder key when no credential is configured, and `Engine::reconfigured` rebuilds through `with_source` so a held host credential is dropped. The module routes credential-less judge configs through a `HostCredential` source over the confidential callback and refuses initialization without an init-bound trusted host. The policy redactor registers only a configured credential as a redaction literal. Docs and the spec describe the host-supplied path. Features
Tests
FindingsNo active actionable findings. Before mergeNone. How this fits togetherflowchart LR
n0["Stub<br/>changed"]:::changed
n1["Engine<br/>changed"]:::changed
n2["SdkClient<br/>changed"]:::changed
n3["validate<br/>changed"]:::changed
n4["Fake<br/>changed"]:::changed
n5["with_client"]:::impacted
n6["...g_and_reconfiguration_cannot_reuse_scores"]:::impacted
n7["...t_attempts_share_one_atomic_request_limit"]:::impacted
n8["config"]:::impacted
n9["...s_contain_no_registered_or_bearer_secrets"]:::impacted
n10["JudgeClient"]:::impacted
n0 -->|implements| n10
n1 -->|uses| n10
n2 -->|implements| n10
n4 -->|implements| n10
n5 -->|calls| n3
n5 -->|uses| n10
n6 -->|calls| n5
n6 -->|tests| n5
n6 -->|calls| n8
n6 -->|tests| n8
n7 -->|calls| n5
n7 -->|tests| n5
n7 -->|calls| n8
n7 -->|tests| n8
n9 -->|calls| n5
n9 -->|tests| n5
n9 -->|calls| n8
n9 -->|tests| n8
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Agent review detailscritique
security
tests
commits
description
Evidence and run details
|
Summary
JudgeConfig.credentialwas required at init. A host whose credential sits in this module's own keyring couldn't supply it: the keyring is readable only once the module is admitted, and reading it during load deadlocks. So openhuman runs with the judge off, and every call it would judge goes to a person.The credential is now optional. Without it, the module asks the init-bound trusted host for it through a new confidential callback,
JudgeCredentialHost2.Credential.timeout_ms.HOST_CREDENTIAL_TTL_MS, fixed).rejected, so rotation and sign-out are picked up.A configured credential behaves exactly as before.
Related issue
Closes #54.
API or behavior changes
JudgeConfig.credentialis nowOption<Secret>, omitted when absent. The wire is unchanged for hosts that send one. Rust code that buildsJudgeConfigmust writecredential: Some(..).JudgeCredentialRequest2 { reason: Initial | Rejected }andJudgeCredentialResponse2 { credential: Option<Secret> }.callback_names2::JUDGE_CREDENTIAL_HOSTandCREDENTIAL. The host serves them as a confidential callback.tinysecurity-auto:CredentialSource;Engine::with_credential_source;SdkClient::with_credential;HOST_CREDENTIAL_TTL_MS;Error::Rejected, which the SDK client now returns for an authentication failure in place ofUnavailable.tinysecurity-auto/README.mdand the spec.CONTRACT_VERSIONstays (2, 1). Maintainers may bump it at release.Validation
Commands actually run, on our Linux CI runner (rustc 1.99.1,
--locked, RUSTFLAGS unset) at cafb02a:cargo fmt --all -- --check: cleancargo clippy --all-targets --all-features -- -D warnings: 0 warnings (also with--keep-going)tinysecurity-auto judge::tests18/0;tinysecurity-bus policy::tests12/0;tinysecurity-module adapter::judge::tests2/0;adapter::tests::approval_tests20/0;tinysecurity-policy285/0cargo build -p tinysecurity-module --examples --all-features: okcargo test --all-features: 1371 passed, 0 failedAuthenticationmapped toUnavailable, exactly the 401 test failsTests
a_hosted_judge_asks_on_first_use_and_reuses_the_credential_for_five_minutes: no ask at construction, one ask, reuse up to the TTL, then a new ask.no_usable_host_credential_sends_the_judgement_to_a_person: covers None and a too-short credential.a_rejected_credential_is_dropped_and_the_next_ask_says_soa_reinit_drops_the_host_credentiala_judge_without_credential_needs_a_host_source: also checks validate_config and the redacted Debug output.an_endpoint_refusing_the_credential_is_reported_as_rejected: a loopback 401.the_host_supplies_the_judge_credential_with_the_reason_it_is_asked;no_credential_or_no_serving_host_gives_none;credential.Documentation
crates/tinysecurity-auto/README.mdanddocs/specs/security-module.mddescribe the host-supplied credential.Host follow-up (openhuman, after a release):
JudgeCredentialHost2.Credentialfrom the session token through the admitted keyring path, with a Keyring reads need a consent per operation, with no session-scoped grant #55 read grant;Checklist
#[allow(...)],#[ignore], or relaxed lints.envcontents in the diff or the description