Skip to content

fix(rest): serve API docs with ReDoc so zip leaves the tree - #440

Merged
hyperpolymath merged 4 commits into
mainfrom
claude/new-session-th4e9w
Oct 19, 2026
Merged

hyperpolymath merged 4 commits into
mainfrom
claude/new-session-th4e9w

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Summary

This fixes the zip advisories at their source instead of ignoring them. CodeRabbit's autofix in #439 added .cargo/audit.toml, which ignores RUSTSEC-2026-0367, 0368 and 0369 in zip. Those advisories are patched only in zip >= 9.0.0, and no fixed 8.x release exists. Every utoipa-swagger-ui release up to 10.0.1 build-depends on zip ^8.6, so a version bump cannot fix it. This PR swaps Swagger UI for utoipa-redoc 7.0 (same utoipa ^6, axum ^0.8), which bundles no archive. The owner chose this option.

Changes

  • src/interfaces/rest: the raw spec is still served at /api-docs/openapi.json, now by an explicit route. ReDoc renders it at /redoc, which replaces /swagger-ui.
  • Cargo.lock: zip, flate2, zopfli, zlib-rs, miniz_oxide, crc32fast, adler2, simd-adler32, typed-path, mime_guess and utoipa-swagger-ui are removed, and utoipa-redoc is added.
  • .cargo/audit.toml is deleted, so no advisory ignores remain.
  • README.adoc and src/interfaces/README.adoc now name the new docs URL.

The Julia Project.toml UUID change from the same autofix was checked and kept. It is a correct ADR-008 profile C UUIDv8, SHA-256("julia-package:hyperpolymath/echidna") with the version and variant bits set, and nothing referenced the old v4 ID.

RSR Quality Checklist

Required

  • Tests pass: cargo check/clippy -D warnings on echidna-rest pass. The crate has no tests of the docs route.
  • Code is formatted (cargo fmt --check)
  • Linter is clean
  • No banned language patterns
  • No unsafe blocks added
  • No banned functions
  • SPDX headers unchanged on modified files
  • No secrets

As Applicable

  • Documentation updated for user-facing changes (docs URL /swagger-ui → /redoc)
  • New dependency reviewed for licence compatibility: utoipa-redoc is MIT/Apache-2.0, from the same project as utoipa.
  • cargo audit: clean, exit 0, with no ignores.

Generated by Claude Code

utoipa-swagger-ui (every release to 10.0.1) builds against zip ^8.6, and
RUSTSEC-2026-0367/0368/0369 are fixed only in zip >= 9. No patched 8.x
exists, so #439 had to ignore all three in .cargo/audit.toml.

Serve the OpenAPI spec at /api-docs/openapi.json as before and render it
with utoipa-redoc at /redoc. zip, flate2 and seven other build-only crates
drop out of Cargo.lock, and the audit ignores are deleted: cargo audit is
clean with no exemptions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019a95kk88TZ1iegsBK3kox3

hyperpolymath commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner Author

Red checks on this PR that are not this PR's, and what happened to each:

  • governance / Validate Hypatia Baseline: Hypatia rejects lockfile format v0.0.3 with {:unsupported_version, "v0.0.3"}; red on main too. The fix is fix(actions_lock): accept gh-actions-lock v0.0.3 lockfiles hypatia#932, itself blocked until hypatia's own actions.lock is repaired.
  • T1 Guix manifest check: apt-get cannot locate a guix package on the runner; red on main; this PR touches no Guix manifest.
  • governance / JS runtimes — environment provisions Bun first: the governance reusable builds the standards js-runtimes tool from a checkout inside this tree, and at the pinned standards commit that crate is not its own workspace root. Red on main. The source fix is hyperpolymath/standards#1299 (merged); CodeRabbit's 78a70c2 on this branch excludes the checkout path from the Cargo workspace meanwhile, which makes this job green here and can go once the governance pin moves past #1299.
  • Rust Build — Real Chapel Library (allow-fail, L2.3+ gate): continue-on-error: true (allow-fail under CI: allow-fail units need an owner and an expiry (ultraplan Phase 2.3) #416, expires 2026-11-30): the link fails on the chapel symbols the real library does not yet export, the job is recorded as failed, and the workflow passes. Same on main.

The CodeRabbit status ("Review state could not be read") is a problem on the bot's side.


Generated by Claude Code

@hyperpolymath
hyperpolymath marked this pull request as ready for review October 19, 2026 20:21
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T22:20:01.122267Z 764d109 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 564e7a54-cb2a-486b-8a32-788897b160d6

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 8c94bcc1-e68f-4757-968a-7dfc376e0178
📥 Commits

Reviewing files that changed from the base of the PR and between ed6ef60 and 72103e4.

⛔ Files ignored due to path filters: 1
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing: 5
  • .cargo/audit.toml
  • README.adoc
  • src/interfaces/README.adoc
  • src/interfaces/rest/Cargo.toml
  • src/interfaces/rest/main.rs
💤 Files with no reviewable changes: 1
  • .cargo/audit.toml

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📜 Recent review details
⚠️ CI failures not shown inline: 7

GitHub Actions: Governance / 0_governance _ Validate Hypatia Baseline.txt: fix(rest): serve API docs with ReDoc so zip leaves the tree

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1mif [ ! -d "$HOME/hypatia" ]; then�[0m
 �[36;1m  git init "$HOME/hypatia"�[0m
 �[36;1m  git -C "$HOME/hypatia" remote add origin https://github.com/hyperpolymath/hypatia.git�[0m
 �[36;1m  git -C "$HOME/hypatia" fetch --depth 1 origin "$HYPATIA_SHA"�[0m
 �[36;1m  git -C "$HOME/hypatia" checkout --detach FETCH_HEAD�[0m
 �[36;1mfi�[0m
 �[36;1mACTUAL_SHA=$(git -C "$HOME/hypatia" rev-parse HEAD)�[0m
 �[36;1mif [ "$ACTUAL_SHA" != "$HYPATIA_SHA" ]; then�[0m
 �[36;1m  echo "::error::Hypatia cached source does not match the resolved commit"�[0m
...
 ##[group]Run cd "$HOME/hypatia"
 �[36;1mcd "$HOME/hypatia"�[0m
 �[36;1mif [ ! -x hypatia ]; then�[0m
 �[36;1m  if ! (mix deps.get && mix escript.build); then�[0m
 �[36;1m    echo "::error::Hypatia scanner build failed at commit $(git rev-parse HEAD) — see upstream hyperpolymath/hypatia"�[0m
 �[36;1m    exit 1�[0m
 �[36;1m  fi�[0m
 �[36;1mfi�[0m
 �[36;1mif [ ! -x hypatia ]; then�[0m
 �[36;1m  echo "::error::Hypatia scanner binary is missing after build"�[0m
...
 ##[group]Run echo "Scanning repository: hyperpolymath/echidna (checking baseline)"
...
 �[36;1m# scan's own exit code…�[0m
 �[36;1mHYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.raw.json || true�[0m
 �[36;1m# …but never swallow a genuine scanner crash into a false pass: require a�[0m
 �[36;1m# valid JSON array before trusting the output as "the findings".�[0m
 �[36;1mif ! jq -e 'type == "array"' hypatia-findings.raw.json >/dev/null 2>&1; then�[0m
 �[36;1m  echo "::error::hypatia scan did not produce a valid JSON findings array (scanner error, not a baseline result)"�[0m
...
       "severity": "medium",
       "baseline_status": "acknowledged",
       "baseline_note": "pre-existing on main; grandfathered at re-arm, burn down under #314",
       "baseline_tracking_issue": "hyperpolymath/echidna#314"
     },
 ##[error]Gate failed: 3 unfiltered finding(s) at or above 'info' (3 ...

GitHub Actions: Governance / 6_governance _ Well-Known (RFC 9116 + RSR).txt: fix(rest): serve API docs with ReDoc so zip leaves the tree

Conclusion: failure

View job details

##[group]Run SECTXT=""
 �[36;1mSECTXT=""�[0m
 �[36;1m[ -f ".well-known/security.txt" ] && SECTXT=".well-known/security.txt"�[0m
 �[36;1m[ -f "security.txt" ] && SECTXT="security.txt"�[0m
 �[36;1mif [ -z "$SECTXT" ]; then�[0m
 �[36;1m  echo "::warning::No security.txt found."�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mgrep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; }�[0m
 �[36;1mif ! grep -q "^Expires:" "$SECTXT"; then�[0m
 �[36;1m  echo "::error::Missing Expires field"�[0m
...
 �[36;1mfi�[0m
 �[36;1mEXPIRES=$(grep "^Expires:" "$SECTXT" | cut -d: -f2- | tr -d ' ' | head -2)�[0m
 �[36;1mif date -d "$EXPIRES" > /dev/null 2>&1; then�[0m
 �[36;1m  DAYS=$(( ($(date -d "$EXPIRES" +%s) - $(date +%s)) / 86400 ))�[0m
 �[36;1m  if [ $DAYS -lt 0 ]; then�[0m
 �[36;1m    echo "::error::security.txt EXPIRED"�[0m
...
 ##[group]Run MIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com|lol/|node_modules/|third-party/|vendor/' | head -5)
 �[36;1mMIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com|lol/|node_modules/|third-party/|vendor/' | head -5)�[0m
 �[36;1mif [ -n "$MIXED" ]; then�[0m
 �[36;1m  echo "::error::Mixed content (HTTP in HTML)"�[0m

GitHub Actions: Governance / 14_governance _ Code quality + docs.txt: fix(rest): serve API docs with ReDoc so zip leaves the tree

Conclusion: failure

View job details

##[group]Run set -eo pipefail
 �[36;1mset -eo pipefail�[0m
 �[36;1m# Arming policy, and the evidence it rests on: standards#991.�[0m
 �[36;1m#�[0m
 �[36;1m#   retired-filename -> BLOCKS. A STABLE predicate:�[0m
 �[36;1m#   the retired `.a2ml` spelling of the launcher standard was�[0m
 �[36;1m#   deleted upstream on 2026-09-22�[0m
 �[36;1m#   (standards#952) and stays deleted, so a caller that is clean�[0m
 �[36;1m#   today cannot become defective without editing the citation�[0m
 �[36;1m#   itself. Measured 2026-09-22 over EVERY clone in the estate --�[0m
 �[36;1m#   595 scanned, 553 carrying an origin/main. 432 reference this�[0m
 �[36;1m#   reusable workflow, but only 12 do so at a MUTABLE ref (@main),�[0m
 �[36;1m#   and a new step reaches ONLY those 12: a caller pinned at a SHA�[0m
 �[36;1m#   freezes this whole file, this step included, so it can never�[0m
 �[36;1m#   receive the step at all. The real gate was run against all 12:�[0m
 �[36;1m#   12/12 rc=0, retired=0. Five slugs do carry the retired literal�[0m
 �[36;1m#   (tma-mark2, canonical-ums, the-nash-equilibrium,�[0m
 �[36;1m#   launch-scaffolder, trigger) and their overlap with the armed 12�[0m
 �[36;1m#   is ZERO -- so arming this tier reds ZERO live callers. A�[0m
 �[36;1m#   known-answer positive control fired (rc=1) on three of those�[0m
 �[36;1m#   defective repos through the identical harness, so the twelve�[0m
 �[36;1m#   zeros are a real measurement and not a broken probe.�[0m
 �[36;1m#�[0m
 �[36;1m#   stale-version -> WARNS, and does not block. A TIME-DEPENDENT�[0m
 �[36;1m#   predicate: the gate compares against its own CURRENT_VERSION, so�[0m
 �[36;1m#   every correctly-citing caller flips to defect the moment the�[0m
 �[36;1m#   standard bumps, having done nothing. A baked-in cutoff DATE does�[0m
 �[36;1m#   not cure that -- the #505 split above can use one because its�[0m
 �[36;1m#   missing-CONTRIBUTING population is static, while this population�[0m
 �[36;1m#   is regenerated at every...

GitHub Actions: Governance / 15_governance _ Security policy checks.txt: fix(rest): serve API docs with ReDoc so zip leaves the tree

Conclusion: failure

View job details

##[group]Run set -uo pipefail
 �[36;1mset -uo pipefail�[0m
 �[36;1mDIR=.github/canonical-references�[0m
 �[36;1mif [ ! -d "$DIR" ]; then�[0m
 �[36;1m  echo "ℹ️  [R5] no $DIR/ — skipped (repo has not opted in)"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1m# Rule files are read with yq, never a hand parser (YAML-POLICY Y-1); this�[0m
 �[36;1m# gate previously parsed them with Python, which the estate bans.�[0m
 �[36;1mif ! command -v yq >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then�[0m
 �[36;1m  echo "❌ [R5] yq and jq are required on the runner for YAML rule parsing"�[0m
 �[36;1m  exit 2�[0m
 �[36;1mfi�[0m
 �[36;1mmapfile -t files < <(find "$DIR" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) ! -name '.*' | LC_ALL=C sort)�[0m
 �[36;1mif [ "${#files[@]}" -eq 0 ]; then�[0m
 �[36;1m  echo "ℹ️  [R5] $DIR/ has no .yml/.yaml rules — skipped"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1m�[0m
 �[36;1merr=$(mktemp)�[0m
 �[36;1mtrap 'rm -f "$err"' EXIT�[0m
 �[36;1mtotal=0�[0m
 �[36;1mfor rf in "${files[@]}"; do�[0m
 �[36;1m  if ! cfg=$(yq -o json '.' "$rf" 2>&1); then�[0m
 �[36;1m    echo "❌ [R5] $rf: not parseable YAML: $cfg"; total=$((total + 1)); continue�[0m
 �[36;1m  fi�[0m
 �[36;1m  if [ "$(jq -r 'type' <<<"$cfg")" != object ]; then�[0m
 �[36;1m    echo "❌ [R5] $rf: top-level must be a mapping"; total=$((total + 1)); continue�[0m
 �[36;1m  fi�[0m
 �[36;1m  rid=$(jq -r --arg b "$(basename "$rf")" 'if has("id") then .id | tostring else $b end' <<<"$cfg")�[0m
 �[36;1m  desc=$(jq -r '.description // ""' <<<"$cfg")�[0m
 �[36;1m  canon=$(jq -r '.canonical_pointer // ""' <<<"$cfg")�[0m
 �[36;1m  mapfile -t pats < <(jq -r '(.patterns // [])[]' <<<"$cfg")�[0m
 �[36;1m  mapfile -t includes < <(jq -r '((.scope // {}).include // [])[]' <<<"$cfg")�[0m
 �[36;1m  if [ "${#pats[@]}" -eq 0 ] || [ "${#includes[@]}" -eq 0 ]; then�[0m
 �[36;1m    echo "❌ [R5:$rid] missing patterns or scope.include in $rf"�[0m
 �[36;1m    total=$((total + 1)); continue�[0m
 �[36...

GitHub Actions: Governance / 16_governance _ Actions lockfile verify.txt: fix(rest): serve API docs with ReDoc so zip leaves the tree

Conclusion: failure

View job details

##[group]Run set -uo pipefail
 �[36;1mset -uo pipefail�[0m
 �[36;1m# Stage the gate, the verifier and the exemption ledger. When THIS�[0m
 �[36;1m# repository is standards, its own working tree already holds all�[0m
 �[36;1m# three (self-lint); every other caller uses the pinned checkout.�[0m
 �[36;1mif [ "$GITHUB_REPOSITORY" = hyperpolymath/standards ]; then�[0m
 �[36;1m  SRC=scripts�[0m
 �[36;1m  LEDGERSRC=.machine_readable�[0m
 �[36;1m  echo "Using this repository's own gate + verifier + ledger (standards self-lint)."�[0m
 �[36;1melse�[0m
 �[36;1m  SRC=.standards-lock/scripts�[0m
 �[36;1m  LEDGERSRC=.standards-lock/.machine_readable�[0m
 �[36;1mfi�[0m
 �[36;1mfor f in check-actions-lock-gate.sh update-actions-lock.sh; do�[0m
 �[36;1m  if [ ! -f "$SRC/$f" ]; then�[0m
 �[36;1m    echo "::error::actions-lock gate: $f not found in $SRC (pinned standards checkout failed?)"�[0m

GitHub Actions: Governance / 18_governance _ JS runtimes — environment provisions Bun first.txt: fix(rest): serve API docs with ReDoc so zip leaves the tree

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1m# Bootstrap rule (as debt-ratchet): standards builds the tool from�[0m
 �[36;1m# the PR's own tree; every other caller from the pinned checkout,�[0m
 �[36;1m# never from a same-named directory of its own.�[0m
 �[36;1mif [ "hyperpolymath/echidna" = "hyperpolymath/standards" ]; then SRC=tools/js-runtimes; else SRC=.standards-js/tools/js-runtimes; fi�[0m
 �[36;1mcargo install --quiet --locked --path "$SRC" --root "$RUNNER_TEMP/jsr"�[0m
 �[36;1mrm -rf .standards-js�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 info: syncing channel updates for 1.99.0-x86_64-unknown-linux-gnu
 info: latest update on 2026-10-01 for version 1.99.0 (b940084d7 2026-09-28)
 info: downloading 6 components
 warn: the missing active toolchain `1.99.0-x86_64-unknown-linux-gnu` has been auto-installed
 warn: this might cause rustup commands to take longer time to finish than expected
 info: you may opt out with `RUSTUP_AUTO_INSTALL=0` or `rustup set auto-install disable`
 error: current package believes it's in a workspace when it's not:
 current:   /home/runner/work/echidna/echidna/.standards-js/tools/js-runtimes/Cargo.toml
 workspace: /home/runner/work/echidna/echidna/Cargo.toml
 this may be fixable by adding `.standards-js/tools/js-runtimes` to the `workspace.members` array of the manifest located at: /home/runner/work/echidna/echidna/Cargo.toml
 Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.
 ##[error]Process completed with exit code 101.

GitHub Actions: Governance / 19_governance _ Workflow security linter.txt: fix(rest): serve API docs with ReDoc so zip leaves the tree

Conclusion: failure

View job details

##[group]Run # GitHub Actions REJECTS a workflow with duplicate keys: the run is
 �[36;1m# GitHub Actions REJECTS a workflow with duplicate keys: the run is�[0m
 �[36;1m# `failure` with no jobs, no log and no check run. Nothing else here�[0m
 �[36;1m# can see it, because yaml.safe_load silently keeps the LAST�[0m
 �[36;1m# duplicate and reports success — so the file "parses" and every�[0m
 �[36;1m# other lint passes. Measured 2026-08-05: nine workflows in hypatia�[0m
 �[36;1m# were dead this way, including a CodeQL workflow with zero�[0m
 �[36;1m# successful runs in its entire lifetime.�[0m
 �[36;1mset -euo pipefail�[0m
 �[36;1mSCRIPT=".standards-dupkey/scripts/check-workflow-duplicate-keys.sh"�[0m
 �[36;1m# Self-hosting fallback: when THIS repository is standards, its own�[0m
 �[36;1m# working tree already holds the script, and during a rename that copy�[0m
 �[36;1m# is the only correct one — the pinned main checkout still has the old�[0m
 �[36;1m# name. Preferring the fetched copy keeps every other caller on the�[0m
 �[36;1m# canonical version.�[0m
 �[36;1mif [ ! -f "$SCRIPT" ] && [ -f scripts/check-workflow-duplicate-keys.sh ]; then�[0m
 �[36;1m  SCRIPT="scripts/check-workflow-duplicate-keys.sh"�[0m
 �[36;1m  echo "Using this repository's own copy (standards self-lint)."�[0m
 �[36;1mfi�[0m
 �[36;1mif [ ! -f "$SCRIPT" ]; then�[0m
 �[36;1m  echo "::error::duplicate-key checker not found — neither fetched from" \�[0m
🧰 Additional context used
📚 Code guidelines: 1
.github/copilot-instructions.md — unknown
📓 Path-based instructions: 2
Source excerpt: Rust: no `transmute` unless FFI with `// SAFETY:` comment

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • src/interfaces/rest/main.rs
Source excerpt: SPDX: `MPL-2.0` on all new files.

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Files:

  • README.adoc
  • src/interfaces/rest/Cargo.toml
  • src/interfaces/README.adoc
  • src/interfaces/rest/main.rs
🔇 Additional comments: 5
src/interfaces/rest/Cargo.toml: 1

43-43: LGTM!

src/interfaces/rest/main.rs: 2

17-17: LGTM!

Also applies to: 123-125, 133-133

127-127: 🔒 Security & Privacy | 🛡️ Detected with Advanced Tier

Security Misconfiguration

Reachability: External
Exploitability: Difficult
CWE: CWE-829 — Inclusion of Functionality from Untrusted Control Sphere

Keep the ReDoc script under application control.

Redoc::with_url serves a page that loads redoc.standalone.js from the CDN’s unpinned latest path. If an attacker controls that response, the script runs in the API origin when a user opens /redoc and can access the proof routes. Serve a pinned local bundle and restrict permitted script sources unless deployment response headers already block the CDN script.

README.adoc: 1

194-194: LGTM!

src/interfaces/README.adoc: 1

40-40: LGTM!

Also applies to: 43-43

📝 Summary

Summary by CodeRabbit

  • New Features
    • The REST API now provides an OpenAPI documentation interface at /redoc and a direct link to the OpenAPI specification at /api-docs/openapi.json.
  • Documentation
    • Updated the REST API documentation to describe ReDoc and link to the specification.

The REST server replaces Swagger UI with ReDoc at /redoc and serves the OpenAPI document at /api-docs/openapi.json. The Swagger dependency and advisory ignore configuration are removed. The REST documentation now identifies ReDoc as the OpenAPI interface.

Changes: REST API documentation

Layer / File(s) Summary
ReDoc dependency and routes
.cargo/audit.toml, src/interfaces/rest/Cargo.toml, src/interfaces/rest/main.rs
The REST server replaces Swagger UI with ReDoc and serves the OpenAPI document at /api-docs/openapi.json. The startup log advertises ReDoc. The Swagger dependency and advisory ignore configuration are removed.
REST documentation
README.adoc, src/interfaces/README.adoc
The documentation identifies ReDoc as the OpenAPI interface and lists the raw OpenAPI specification endpoint.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 72103

The documentation migration has no confirmed merge-blocking issue. Complete the normal checks before merging.

Security architecture risk: 🟡 Moderate · up to 72103

The change is limited to documentation, but the new page loads a mutable external script with the page’s browser privileges. This introduces a supply-chain trust boundary that could affect same-origin API access. Production protections have not been confirmed; server-process compromise is not established.

Retained concerns

  • Medium · security · observed: The default ReDoc page delegates executable browser code to an unpinned third-party “latest” bundle without SRI, replacing locally served Swagger UI assets. This introduces a browser supply-chain trust boundary on the API origin. Whether production controls prevent or constrain that execution remains unresolved.
Security review details

Security Blast Radius

  • inferred — If an attacker controls the upstream bundle delivered to a documentation visitor and execution is permitted, the code inherits the documentation page’s browser-origin privileges. Exposure includes affected visitors and API operations their browsers can perform under effective authorization rules. This does not itself grant Rust-process authority, infrastructure credentials, or access to other tenants or environments.

Security Findings and Attack Paths

  • inferred — The supported attack path requires control over the external script’s delivered contents and a visitor loading /redoc with execution allowed. The supplied candidate remains deferred: production headers and other runtime controls are missing, and no successful compromise or credential exposure is established.

Trust Boundaries and Controls

  • observed — The default template has no script integrity attribute, and the application does not select custom HTML or compose a CSP layer. The inspected repository Caddy configuration adds HSTS, nosniff, frame denial, and referrer policy, but no CSP. Those general headers do not authenticate the script bytes; actual deployed response transformation remains unknown.

Resilience and Maintainability Implications

  • inferred — The browser bundle can change independently of the locked Rust dependency and server release. This creates documentation-code and security-control drift outside the application’s normal release and rollback boundary.

Hardening Proposals

  • proposed — Use ReDoc’s custom HTML support to serve a reviewed, versioned bundle locally, or pin immutable external bytes with integrity verification. Apply a compatible script policy and verify the HTML and headers actually delivered by the production documentation route.
Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly identifies the main change: replacing Swagger UI with ReDoc to remove the zip dependency.
Description check Passed The description includes the required Summary, Changes, quality checklist, and testing information. It explains the advisory fix, dependency changes, documentation updates, and reported validation res…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (3 skipped: 3 …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes the required Summary, Changes, quality checklist, and testing information. It explains the advisory fix, dependency changes, documentation updates, and reported validation results. Screenshots are not required for this change.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (3 skipped: 3 unsupported.)

✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
📝 Generate docstrings
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72103e4990

ℹ️ 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".

"/api-docs/openapi.json",
get(|| async { axum::Json(ApiDoc::openapi()) }),
)
.merge(Redoc::with_url("/redoc", ApiDoc::openapi()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serve the ReDoc renderer locally

Redoc::with_url uses utoipa-redoc's default HTML template, which loads redoc.standalone.js from the mutable https://cdn.redoc.ly/redoc/latest/... URL. Consequently, /redoc remains blank whenever the browser cannot access the public CDN—such as in an air-gapped or outbound-filtered deployment—even though the API and embedded specification are available locally; the previous Swagger UI served its build-time-bundled assets from this process. Please use a custom template backed by a locally served, pinned ReDoc bundle so the documented endpoint remains self-contained.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and real: utoipa-redoc 7.0's default template (res/redoc.html in the crate) is

<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>

so /redoc is a page we serve that executes whatever latest resolves to at request time, with no integrity attribute. The previous Swagger UI embedded its assets at build time, so this PR moved the dependency from build time to run time. Two consequences: the page is blank on an air-gapped or egress-filtered deployment, and the bundle is a mutable third-party script on a page served from our origin.

The fix I propose, in a follow-up on this stack rather than here (this PR's point is dropping the vendored zip):

  1. Redoc::with_url(...).custom_html(...) with the same template but the version pinned (/redoc/v2.x.y/bundles/...) and an integrity="sha384-…" attribute, computed from the bundle bytes. That keeps the tree free of the bundle and makes the dependency immutable.
  2. Optionally an ECHIDNA_REDOC_BUNDLE path so an operator can serve a local copy instead of the CDN; the ENV-VARS doc gets the entry.

I cannot compute the SRI hash from this session (the CDN is not reachable through its egress policy), so I am not pushing a half-pin. @hyperpolymath: say whether you want (1) alone or (1)+(2), and I will open it against this branch's merge.


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

✅ Coding Agent task started: View task and status

The task will inspect the CI failures, validate its fix, and commit the fix to this branch automatically.

Note: Fixing CI failures is a beta feature and may encounter errors. Expect some limitations and changes as we gather feedback and continue to improve it.

⏭️ Checks skipped: 2 · already failing on `main` (not caused by this PR)
  • GitHub Actions: Governance / 0_governance _ Validate Hypatia Baseline.txt
  • GitHub Actions: Governance / 6_governance _ Well-Known (RFC 9116 + RSR).txt

@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Add Carrot credits or activate Agent usage billing to use Autofix

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

🤖 Completed: Generate docstrings for PR #440 — View commit 1092345

@hyperpolymath
hyperpolymath enabled auto-merge (squash) October 19, 2026 22:20
hyperpolymath pushed a commit that referenced this pull request Oct 19, 2026
#440 carries the same exclude (its CI-fix commit 78a70c2) and merges first;
byte-identical text here means the two land without a conflict. It can go
once the governance pin reaches hyperpolymath/standards#1299, which makes
that crate its own workspace root.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019a95kk88TZ1iegsBK3kox3
@hyperpolymath
hyperpolymath merged commit d8a0185 into main Oct 19, 2026
79 of 83 checks passed
@hyperpolymath
hyperpolymath deleted the claude/new-session-th4e9w branch October 19, 2026 23:03
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.

2 participants