1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
[]
= "feather-reader"
= "0.4.4"
= "2021"
= "1.94"
= "A minimalist, atproto-native RSS/Atom reader in Rust — your feed subscriptions live in your own PDS."
= ["Justin Stanley"]
= "AGPL-3.0-only"
= "https://github.com/justin-stanley/feather-reader"
= "https://feather-reader.com"
= "README.md"
= ["rss", "atom", "atproto", "feed", "reader"]
= ["web-programming"]
# The crate ships a small library (shared types + module seams) and the server binary.
[]
= "feather_reader"
= "src/lib.rs"
[[]]
= "featherreader"
= "src/main.rs"
[]
# --- async runtime -------------------------------------------------------
= { = "1.45", = ["full"] }
# --- web framework + middleware -----------------------------------------
# `multipart` powers the OPML file-upload import (POST /opml).
= { = "0.8", = ["multipart"] }
# fs = ServeDir/ServeFile for embedded/static assets; trace = request tracing
# layer; set-header = the global security-header layer (CSP + friends).
= { = "0.7", = ["fs", "trace", "set-header"] }
# --- HTTP client (feed fetch) -------------------------------------------
# default-features off to drop the OpenSSL/native-tls path; rustls keeps the
# static-binary story clean. gzip for polite/compressed feed fetches.
= { = "0.13", = false, = ["rustls", "gzip", "json"] }
# --- feed parsing --------------------------------------------------------
= "3.0"
# A specified hash for stored dedup keys (`feed::dedup_hasher`); std's
# `DefaultHasher` may change between Rust releases. Already in the tree via
# feed-rs and phf.
= "1"
# --- HTML sanitization (feeds are hostile input) -------------------------
= "4"
# --- local per-DID SQLite cache -----------------------------------------
# RUNTIME queries only (no compile-time `query!` macros, so no DATABASE_URL at
# build). sqlx 0.9 splits the old `runtime-tokio-rustls` feature into
# `runtime-tokio` + `tls-rustls-ring`. `sqlite-bundled` links SQLite into the
# binary for the single-file deploy story; `migrate` runs embedded migrations.
= { = "0.9", = false, = [
"runtime-tokio",
"tls-rustls-ring",
"sqlite-bundled",
"migrate",
"chrono",
# `derive` pulls in sqlx-macros so the store's row structs can
# `#[derive(FromRow)]` — the derive macro, not just the `FromRow` trait.
"derive",
] }
# --- HTML templating (compile-time, embedded in the binary) --------------
# askama 0.16 dropped the separate `askama_axum` integration crate (deprecated,
# pinned to askama <0.13). `askama_web` provides the axum IntoResponse glue.
= "0.16"
= { = "0.16", = ["axum-0.8"] }
# --- serde ---------------------------------------------------------------
= { = "1", = ["derive"] }
= "1"
# --- error handling ------------------------------------------------------
= "1"
= "2"
# --- tracing -------------------------------------------------------------
= "0.1"
= { = "0.3", = ["env-filter", "fmt"] }
# --- time / dates --------------------------------------------------------
= { = "0.4", = ["serde"] }
# --- URL parsing (feed autodiscovery, canonicalization) ------------------
= "2"
# --- CSPRNG (opaque, unguessable session ids) ----------------------------
# Already in the dependency tree transitively; pinned here as a direct dep so
# the session-id minting reads from the OS CSPRNG rather than a home-rolled RNG.
= "0.4"
# --- at-rest encryption for OAuth secrets --------------------------------
# AES-256-GCM + SHA-256 for the `enc.v1.gcm.` codec that wraps OAuth tokens,
# DPoP key material and the confidential-client signing JWK before they touch
# the SQLite volume. `ring` is ALREADY in the tree (rustls pulls it); pinned as
# a direct dep rather than adding a second AEAD implementation. base64 0.22 is
# likewise already present transitively — pin 0.22, NOT 0.21, so the lock keeps
# a single copy (`deny.toml` sets multiple-versions = "warn", which would not
# catch a duplicate).
#
# **That single copy holds today and is about to stop holding.** The invariant
# is maintained by this comment and nothing else — the `warn` above is the
# reason, and it is deliberate. But the version to match is no longer one
# number: `reqwest` 0.13.5 requires base64 **0.23**, while `sqlx-core` 0.9.0 and
# `hyper-util` 0.1.20 still require 0.22. Whichever this is pinned to, the lock
# carries two copies from the moment reqwest is bumped (pending in the
# cargo-minor-patch group PR) — and it arrives through reqwest, without anyone
# editing this line.
#
# When that lands, expect the duplicate and do not chase it. The copy count is
# two either way — pinned at 0.22 we group with sqlx-core and hyper-util, at
# 0.23 with reqwest — so moving the pin buys nothing. There is no correctness
# dimension: this crate only encodes its own bytes to `String` via
# `general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE_NO_PAD}`, no base64
# type crosses into reqwest's API, and the output is byte-identical across
# versions. Leave it at 0.22 unless a concrete reason appears, and delete this
# paragraph once sqlx-core and hyper-util catch up and one copy is achievable
# again.
= "0.17"
= "0.23"
# Constant-time comparison. ring's `verify_slices_are_equal` is deprecated as an
# internal-only helper; `subtle` is the maintained option and is already in the
# tree, so this adds no package to the lock either.
= "2"
# --- P-256 for the OAuth confidential-client signing key -----------------
# The `jwk` feature is the reason for this dep: it gives first-class JWK
# import/export, so the key file stays in the SAME format the Node sidecar
# writes and a rollback can still read it. The alternative was hand-parsing
# PKCS#8 DER out of `ring` to recover the private scalar — more code, in the
# one place where a silent bug is worst.
#
# NOT `atproto-oauth`/`atproto-identity`: their `jwk`/`dpop`/`pkce` primitives
# are exactly what is wanted, but `reqwest` is a NON-optional dependency of
# both, so depending on either would compile a second HTTP stack (reqwest 0.12
# beside our 0.13) plus hickory-dns into the binary — a network path the SSRF
# guard does not cover — for functions that never touch the network. Version
# 0.13 is the same one `atproto-identity` itself pins.
#
# Cost, stated plainly: 20 pure crypto/encoding packages, and it newly
# duplicates `hmac` and `rand_core` (the RustCrypto 0.10-era stack beside the
# newer one already present). `deny.toml` only warns on multiple versions, so
# this is recorded here rather than left for it to catch.
= { = "0.13", = ["jwk"] }
# --- DNS, for atproto handle resolution ----------------------------------
# Handle -> DID resolves by `_atproto.<handle>` TXT record, and DNS is the
# PREFERRED method per the handle spec -- not a fallback. This is not optional:
# a live PDS test found a real handle whose /.well-known/atproto-did returns 404
# and which resolves by TXT alone, so an HTTP-only client simply cannot log it
# in. `tokio::net::lookup_host` does A/AAAA only, so a resolver is required.
#
# Worth noting against the p256 comment above, which rejected
# atproto-identity partly for pulling hickory: the objection there was never
# hickory itself but reqwest 0.12 riding along with it -- a SECOND HTTP stack
# the SSRF guard does not cover. A DNS resolver adds no such path.
#
# Cost: 21 packages (incl. `moka`, `crossbeam-*`, `ipconfig`), and it newly
# duplicates `core-foundation`. default-features are already the minimum
# (`tokio` + `system-config`); using the host's own resolver configuration is
# the correct behaviour for handle resolution.
= { = "0.26.3", = false, = [
"tokio",
"system-config",
] }
# --- filesystem stat (startup disk-headroom check) -----------------------
# Already in the tree transitively; pinned as a direct dep so the startup
# watermark-vs-disk safety check can call `statvfs(3)` on unix to compare the
# configured DB-size watermark against the DB volume's actual free space. Unix
# only — the target deploys are Linux/Fly and the check no-ops elsewhere.
[]
= "0.2"
[]
# `ServiceExt::oneshot` drives the axum router through a single request in the
# web-layer tests (cache-control headers, rate-limit 429, the pre-handshake
# login refusal, admin-mint authz). Already in the lock transitively; the `util`
# feature exposes `oneshot`.
= { = "0.5", = ["util"] }
# `#[tokio::test(start_paused = true)]` for the relay-walk budget test: it drives
# tokio's clock so a simulated slow relay costs no real time and the assertion is
# exact rather than timing-dependent. Same tokio crate as the runtime dependency
# above — this enables a feature, it does not add a package to the lock.
= { = "1.45", = ["test-util"] }
# A TEST certificate authority, so the test HTTP server can speak real TLS.
#
# The OAuth issuer is required to be `https` (discovery.rs), and the SSRF guard
# refuses loopback — so there was no way to drive the real `complete` against a
# local server, and the adapter wiring between the tested seam and the network
# had no coverage at all. The alternative was a test-only escape from the https
# rule; a real CA SATISFIES the rule instead of suspending it, which is why this
# dependency is here rather than a `#[cfg(test)]` `if scheme != "https"` bypass.
#
# `ring` for the TEST SERVER. reqwest reads the process-installed default and
# falls back to aws-lc-rs, so installing ring in a test DOES affect clients built
# after it in the same binary; release builds install nothing and use aws-lc-rs.
# Both providers are already in the lock — `ring` is a direct dependency of this
# crate and `aws-lc-rs` arrives via rustls — so this adds neither.
= { = "0.14.10", = false, = ["pem", "crypto", "ring"] }
# The server half of that. rustls 0.23 and tokio-rustls are ALREADY in the lock
# transitively via reqwest, so this promotes an existing crate to a direct dev
# dependency rather than adding a second TLS stack — verified: exactly one
# `rustls` package in Cargo.lock.
= { = "0.26", = false, = ["ring"] }