1.0 readiness: integrated batch of 12 reviewed PRs#292
Merged
Conversation
The validator and REQ-DM-MANIFEST-003 both treat `datasets` as optional; only `schema_version` and `catalog` are required. `DatasetManifest` deserializes with `#[serde(default)]` (crates/registry-manifest-core/src/lib.rs:437), `validate_manifest()` applies only an upper-bound count check to datasets, and the spec's MUST at rs-dm-manifest.mdx scopes to `catalog` only. Closes #28 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
JsonlFileSink::new used a 10 MiB x 5 file default rotation cap, retaining only ~50 MiB of audit history before silently discarding the oldest rotated file. Raise DEFAULT_MAX_FILES to 50 (~500 MiB) so the crate default is a reasonable retention window on its own. Registry Relay and Registry Notary both override rotation via JsonlFileSink::with_rotation (100 MB x 14 files) and are unaffected. In-tree, the only caller relying on the crate default is the registry-platform-sts bridge binary, which was silently losing audit history after ~50 MiB. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Waivers and an undeclared deployment profile were previously honored silently at boot: visible only on the posture surface and in doctor, with nothing in the boot log or the audit trail. Now every config load warns per suppressed finding (deployment.gate_waived, with the finding id, reason, and expiry), per expired waiver (deployment.waiver_expired), and per undeclared profile (deployment.profile_undeclared). The serve path additionally writes one operational audit record (deployment.gate_waived) per waived gate once the audit pipeline exists, so a boot that runs with reduced posture leaves durable evidence. Part of #207. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
registry_notary:admin is intentionally global per Registry Notary instance: enforcement is scope-string-only (EvidencePrincipal has no issuer/tenant field), so one admin-scoped credential authorizes admin operations for every credential profile the instance hosts. Partitioned admin authority within a single instance is out of scope before 1.0. The supported multi-tenant isolation model is one Registry Notary instance per issuing authority. Document this in the deployment hardening runbook and disambiguate it from deployment.multi_instance, which declares horizontal scale-out of a single issuing authority's workload (shared Redis-backed replay/audit state) and is not a tenancy boundary. Add admin_scope_is_instance_global_across_credential_profiles, a standalone_http integration test that configures two credential profiles (two issuers, distinct signing keys) on one instance and shows a single registry_notary:admin credential is authorized for the admin credential-status route against both, while a non-admin key is denied for both. This pins the documented behavior so a future change that tries to partition admin scope by profile fails loudly. Closes #58 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A claim source binding with no matching policy (no policy_id and no context constraints) silently falls back to unrestricted, identifier-only resolution per spec RS-DM-CLAIM. Nothing warned the operator. This adds visibility only; claim resolution behavior is unchanged: - registry-notary doctor now emits notary.source_binding.no_matching_policy, naming every claim/binding pair that lacks a matching policy. - A matching deployment gate surfaces the same fact as a finding_warn under production and a finding_error under evidence_grade; local and hosted_lab stay quiet since the fallback is spec-conformant and only becomes an operational concern at higher assurance tiers. Both bound tiers are waivable, so a waiver is the sanctioned way to accept the fallback deliberately. Closes #171 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Add a disabled-by-default, in-process, coarse throttle on repeated authentication failures from one client address (auth.failure_throttle). When enabled, an address that reaches max_failures failed auth attempts within window_seconds gets a stable 429 (auth.rate_limited, with Retry-After) before the auth provider runs, for every request from that address until the window rolls over; successful auth neither counts nor resets. Ingress rate limiting remains the primary control against high-volume abuse; this is a local backstop for deployments without a gateway in front of them, or defense in depth behind one. It does not throttle other expensive routes for authenticated callers, which is deliberately deferred. The throttle keys on the same trust-proxy-aware client address the audit record's remote_addr reports, extracted from audit/mod.rs into a shared crate::net::resolve_remote_addr so both subsystems agree on which address a request came from and untrusted X-Forwarded-For values cannot be used to evade the throttle or split it across fake keys. The map is bounded (capped entries, oldest-window eviction) so it cannot grow without bound under a flood of spoofed source addresses. Disabled by default: when auth.failure_throttle.enabled is false (the default), no throttle is constructed and the auth middleware's behavior is unchanged from before this feature. Part of #78 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Registry Notary had per-request size caps and source limits, but no budget on sustained evaluate/batch_evaluate volume from a single machine principal. Add evidence.machine_quota: a fixed one-minute window, counted in subjects (a single evaluate costs 1, a batch costs items.len()), keyed by principal_id, and enforced only for principals that are not classified as self-attestation. A request whose cost would exceed the remaining budget is rejected whole, so a denied batch never partially consumes the window. Exhaustion returns 429 with the stable code evaluation.quota_exceeded and a Retry-After header. The quota is disabled by default (subjects_per_minute: 6000 when enabled), so existing deployments are unaffected until an operator opts in. Part of #78 (carries former #60 scope). Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
check_security_assurance.py resolved every manifest ref (enforcement test files, route-source files) as products/notary-relative, but the refs in exposure-manifest.json and route-inventory.json point at crates/... paths that only exist at the monorepo root. This has made `just exposure-check` fail unconditionally since the monorepo migration (d2d2cf2); the gate is not wired into root CI, so the breakage was invisible. Fall back to the monorepo root when a ref does not resolve under the product tree, and scope the route-source crate scan to registry-notary* crates so it does not pick up unrelated shared crates (e.g. the registry-platform-testing mock server) that also live under crates/. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The exposure-manifest/route-inventory security gate has never run in root CI, so its breakage during the monorepo migration went unnoticed. Run `just exposure-check` alongside the existing OpenAPI checks now that its path resolution is fixed. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…0.8.4 Exercise the draft operate/upgrade-and-rollback how-to against the lab topology in an isolated compose project: v0.8.3 baseline, doctor-gated upgrade to v0.8.4, verified roll back to v0.8.3. Evidence and the full findings list (config schema break on fingerprint.commitment, stdout audit sink vs 'audit log directory' backup, lazily created anti-rollback state file, and more) live in release/exercises/, which this commit introduces. Ticks the READINESS gate with a link to the evidence. Part of #203. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… attestation A local rotating file audit sink caps retention, and an attacker with host access can destroy the audit trail. Add the deployment.evidence.audit_offhost_shipping attestation and the relay.audit.retention_local_only gate, which fires only for a local file sink without the attestation. Stdout and syslog sinks are exempt: stdout retention is the orchestrator's log pipeline's concern, and syslog forwarding is the syslog daemon's own surface. Bound at finding_warn under production and finding_error under evidence_grade; unbound under local/hosted_lab; waivable like the other audit posture gates. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…g attestation A local file or jsonl audit sink caps retention to whatever the host disk holds, and an attacker with host access can destroy the evidence. Add deployment.evidence.audit_offhost_shipping (default false) so operators can attest that audit events are shipped off-host, and bind a new notary.audit.retention_local_only gate that fires when the sink is file/jsonl and the attestation is absent. stdout and syslog sinks are exempt: their retention is owned by the orchestrator log pipeline or the syslog daemon's own forwarding surface. Severity bindings: unbound under local and hosted_lab, finding_warn under production, finding_error under evidence_grade. Waivable like other finding-tier gates; no startup_fail or readiness_fail. The existing audit_sink_is_durable predicate and notary.audit.sink_missing gate are untouched. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The machine evaluation quota limiter keeps its counters in an in-memory map and each RegistryNotaryApiState builds its own limiter, so the quota is enforced per Notary process, not cluster-wide. Document that N replicas give a caller up to N x subjects_per_minute, and point operators at replica-count sizing or a shared limiter. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…te scope Add the missing infra port env exports and -p registry-upgrade-ex project flag to every recorded compose command so the exercise is actually reproducible alongside the default lab, and record that the doctor gate never checked openfn-civil-notary.yaml (v0.8.4's breaking source-adapter connector rename) as an explicit gap instead of leaving it implied as validated. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The retained-suffix compatibility path trusted the first parsed envelope's prev_hash whenever the oldest file on disk was rotated. In a partial rotation set where the oldest rotated file was truncated to empty, the first envelope came from a newer file, so bootstrap anchored on it and silently accepted a chain missing its leading audit records. Grant the compatibility path only when the oldest rotated file actually supplies the anchor record; an empty oldest file now falls back to genesis-strict verification and fails, surfacing the missing records. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
matching_policy_diagnostics always emitted an explicit notary.source_binding.no_matching_policy diagnostic, even when the active profile already binds that finding through the deployment gate catalog (production: finding_warn, evidence_grade: finding_error). Report assembly never dedups by code, so doctor --profile production/evidence_grade showed the same code twice with divergent severities. Add gate_severity_for_profile to look up whether a gate binds under the active profile, and suppress the explicit diagnostic when it does. local, hosted_lab, and undeclared profiles leave the gate unbound, so they keep the explicit diagnostic's visibility. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Behind a proxy or load balancer with server.trust_proxy left disabled (the default), the throttle keys on the proxy's own socket address, so every client shares one bucket and combined failures from anyone can 429 all clients until the window rolls. Emit a config.validation_warning finding at boot so operators catch the misconfiguration instead of discovering it as a self-inflicted outage. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Clarify that the boot-time deployment.gate_waived audit write is subject to audit.write_policy: fail_closed aborts startup on write failure, availability_first logs and continues, making the durable record best-effort and the per-gate boot log warnings the real floor. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…to 50 files Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> # Conflicts: # products/notary/CHANGELOG.md
…est reference Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…run v0.8.3 to v0.8.4 Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Both fix/relay-boot-gate-visibility (#269) and feat/relay-auth-failure-throttle (#273) added an identical SharedLog/SharedLogWriter tracing capture helper to config/validate.rs tests. The textual auto-merge kept both copies, producing E0119/E0428 duplicate-definition errors. Keep one copy of the shared types and both distinct capture functions (run_with_captured_logs, captured_throttle_validation_logs). Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ota test PR #276's machine_quota_http_test used assert_eq!(x.is_string(), true), which trips clippy::bool_assert_comparison under -D warnings on rust 1.95. Pre-existing on the source branch; surfaces in the integration workspace clippy gate. Replace with assert!(x.is_string()) per the lint's own suggestion. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
This was referenced Jul 7, 2026
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9ac087f58a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The auth-failure-throttle self-DoS warning only fired when server.trust_proxy.enabled was false. With trust_proxy enabled but an empty trusted_proxies list, resolve_remote_addr matches no peer and still ignores X-Forwarded-For, collapsing every forwarded request onto the proxy's socket address: the same shared-bucket 429-everyone condition, warning suppressed. Extend the warning to fire on enabled+empty-list, update the docs paragraph, and add a fourth tracing-capture test. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Codex flagged that deleting the true oldest rotated file leaves an on-disk state byte-identical to a legitimate aged-out rotation: the remaining oldest record has a non-None prev_hash, the retained-suffix compat path anchors on it, and a restart silently accepts the loss of the leading records. Confirmed real but undecidable from local disk alone (middle-of-set gaps are still caught by boundary PrevHashMismatch during concatenated verification; only oldest-file deletion is indistinguishable). Verification behavior is unchanged by design; the compat path now emits a tracing warning so operators can correlate, with off-host audit shipping / external anchoring as the structural mitigation. First platform crate to take a tracing dependency. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The auth.rate_limited code is emitted by Relay's AuthError::RateLimited (the auth.failure_throttle backstop), not by Notary. Notary throttling surfaces self_attestation.rate_limited and evaluation.quota_exceeded instead. Move the row out of the Registry Notary table into Registry Relay > Authentication, matching that table's terser column style. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
auto-merge was automatically disabled
July 7, 2026 08:53
Rebase failed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integrates 12 reviewed PRs into one branch, merged in dependency order with
--no-ff --signoffmerge commits (one per PR), cross-PR conflicts resolved once, and the full verification gate run on the final tip.Integrated PRs
dfb4c6a0just exposure-check(dead since the monorepo migration: manifest refs resolved only against the product tree) and wires it into root CI so it cannot go dark again. Merged first to unbreak the gate for everything after.e42f3ae950d5ae45deployment.gate_waivedoperational audit record per waived gate.d0ddd69fauth.failure_throttle, stable 429auth.rate_limited+Retry-After), keyed on trust-proxy-resolved client address, with a validation warning when enabled withouttrust_proxy.b43ddae2relay.audit.retention_local_onlydeployment gate +deployment.evidence.audit_offhost_shippingattestation; local rotatingfileaudit sink without the attestation raises a posture finding (warn/production, error/evidence_grade).bf14bee1doctorwarning +notary.source_binding.no_matching_policydeployment gate for claim source bindings that declare no matching policy; resolution behavior unchanged, visibility only.83ff4e3bnotary.audit.retention_local_onlygate +deployment.evidence.audit_offhost_shippingattestation forfile/jsonlaudit sinks.47f608acevidence.machine_quota: per-principal, subject-counted fixed-window quota over machineevaluate/batch_evaluate, 429evaluation.quota_exceeded+Retry-After; disabled by default; self-attestation principals exempt.3b949360datasetsas optional.806074aaregistry_notary:adminscope is instance-global (not per credential profile) and adds a pinning integration test.f4978d6786f1d883Review history
Each of the 12 PRs was reviewed by the Codex bot with all findings fixed and answered on-thread, then re-reviewed by expert reviewers (relay, notary, and docs panels). The expert-review findings — #268 retained-suffix hardening, #273 trust_proxy warning, #271 doctor double-report, #274 link cap, #280 exercise-record linking — were all fixed on the source branches before integration; the branch SHAs in the table above are the post-review heads, verified against the expected SHAs at integration time.
Conflict resolutions (done once, here)
crates/registry-notary-core/src/deployment.rs/config.rs(feat(notary): surface source bindings without a matching policy #271 + feat(notary): gate local-only audit retention behind off-host shipping attestation #283): both newGateInputfields, both finding consts, both gate-catalog entries, feat(notary): gate local-only audit retention behind off-host shipping attestation #283'sDeploymentEvidenceConfig, feat(notary): surface source bindings without a matching policy #271'sgate_severity_for_profile(), and bothgate_input()projections kept, per the reviewer-specified merged content.products/notary,crates/registry-relay) and operator/config docs: all bullets/rows/sections from all PRs kept under a single heading set; nothing dropped.crates/registry-relay/src/config/validate.rs: fix(relay): make waived deployment gates loud at boot #269 and feat(relay): add local auth-failure throttle #273 each added an identicalSharedLog/SharedLogWritertest helper; the textual merge kept both copies (duplicate-definition compile error). Deduplicated to one copy of the types plus both capture functions, as a labeled integration-fix commit.crates/registry-notary-server/tests/machine_quota_http_test.rs(feat(notary): add per-principal machine evaluation quota #276): two pre-existingassert_eq!(x.is_string(), true)lines tripclippy::bool_assert_comparisonunder-D warnings; replaced withassert!(...)per the lint suggestion, as a labeled integration-fix commit.Security review notes (consolidated)
These PRs touch security-sensitive areas (deployment posture gates, audit integrity, authentication throttling, evaluation quota) and carried explicit per-PR notes; condensed here:
startup_failgates remain unwaivable; expiry comparison unchanged. Adds logging and an audit record for outcomes that already existed.429 auth.rate_limitedadded to the error taxonomy with exhaustive variant/status tests. Throttle keys on the same trust-proxy-resolved client address audit already uses, so untrustedX-Forwarded-Forcannot frame another address or evade the throttle. Throttled short-circuits are themselves audited. Data-plane and admin apps use independent throttle instances (flagged for reviewers as intentional).finding_erroris a posture-only tier (no startup/readiness change); the gate is waivable with the standard mandatory-reason, mandatory-expiry waiver mechanism, likerelay.audit.best_effort.local/hosted_lab;audit_offhost_shippingdefaults tofalse, so an operator who does nothing keeps identical runtime behavior, just with an added posture finding where applicable.sub); quota keys onprincipal_id, which underauth.mode: oidcis caller-supplied (JWTsub/principal_claim) — the same identity already used for audit, now also a rate-limit key; fails closed (oversized key treated as over-quota); no change to authentication, credential issuance, signing, or audit redaction.Verification (run on the final tip)
cargo fmt --checkcargo check --locked --workspace --all-targetscargo clippy --workspace --all-targets -- -D warningscargo test --locked --workspacejust openapi-check(products/notary)just openapi-contract(crates/registry-relay)OPENAPI_CONTRACT_BASE_REFunset, breaking-change diff skipped as usual locally)just exposure-check(products/notary)npm test(docs/site)npm run check(docs/site)build:archives, which failed only on a sandbox-blocked network clone (ssh to github.com timed out while fetching an external repo pin);check:seo:built(244 latest + 1192 archived HTML files) andcheck:links:built(254,850 internal links/assets) then run individually — passpython3 -m unittest release/scripts/test_registry_release.pyREGISTRY_LAB_RELEASE_SOURCE_MODE=monorepo lab/scripts/check-release-source-model.shMerging this PR
Per-item revert granularity is preserved via the 12 per-PR merge commits on this branch. Merge this PR with a merge commit, not squash, so each integrated PR remains individually revertable.
Closes #207
Closes #78
Closes #171
Closes #28
Closes #58