[package] name = "makenotwork" version = "0.16.3" edition = "2024" license = "LicenseRef-PolyForm-Noncommercial-1.0.0" # Server binary: never published to a registry. Marks the crate private so # supply-chain tooling (cargo-deny) treats its first-party path deps correctly. publish = false [features] # Cheap Argon2id parameters (8 MiB, 1 iteration) so a suite that seeds hundreds # of password hashes is not spending ~600ms on each. Scoped to `auth::hash_password` # and nothing else: verification reads its parameters from the hash string, so # the production verifier is still what runs. # # It used to swap the rate-limit constants too, which meant the limiter under # test was never the limiter that ships. Those are runtime config now # (`constants::RateLimits`), so this feature can no longer change how the server # behaves under load. Do not add anything to it that can. fast-tests = [] [dependencies] # Async trait (for StorageBackend trait object) async-trait = "0.1" # Web framework axum = { version = "0.8.8", features = ["macros"] } axum-extra = { version = "0.12.6", features = ["cookie", "form", "query", "typed-header"] } # OpenAPI spec generation utoipa = { version = "5", features = ["axum_extras", "chrono", "uuid"] } utoipa-swagger-ui = { version = "9", features = ["axum"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" toml = "1.1" # `process` is used directly (git_ssh, build_runner). It was previously arriving # only by feature unification through an S3 dependency, so it belongs here. tokio = { version = "1.50.0", features = ["macros", "rt-multi-thread", "net", "signal", "process"] } tokio-stream = { version = "0.1", features = ["sync"] } tokio-util = { version = "0.7", features = ["io"] } tower = "0.5.3" tower-http = { version = "0.7.0", features = ["trace", "fs", "limit", "request-id", "propagate-header", "set-header"] } tracing = "0.1.44" tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] } # Templates askama = "0.16.0" # Environment & Configuration dotenvy = "0.15.7" # Database sqlx = { version = "0.8.6", features = ["runtime-tokio", "postgres", "uuid", "chrono", "migrate", "macros"] } uuid = { version = "1.22.0", features = ["v4", "serde"] } chrono = { version = "0.4.44", features = ["serde"] } # Authentication argon2 = "0.5.3" tower-sessions = { version = "0.14.0", features = ["axum-core"] } tower-sessions-sqlx-store = { version = "0.15.0", features = ["postgres"] } # Concurrent hash map (session touch cache) dashmap = "6" # Rate Limiting tower_governor = "0.8.0" governor = "0.10.4" # JWT (SyncKit) jsonwebtoken = { version = "10.4.0", features = ["rust_crypto"] } # TOTP / 2FA totp-rs = { version = "5.7", features = ["qr"] } # WebAuthn / Passkeys # 0.6 is where webauthn-rs drops openssl for crypto-glue (RustCrypto), which is # what gets openssl-sys out of the tree and off cargo-deny's ban list. Still a # `-dev` prerelease upstream, so the version is pinned exactly rather than left # to float. The stored `user_passkeys.credential_json` format is unchanged: a # 0.5-written Passkey round-trips through 0.6 byte-identically, and 0.5 still # reads what 0.6 writes, so no migration and a rollback stays open. webauthn-rs = { version = "=0.6.1-dev", features = ["danger-allow-state-serialisation", "conditional-ui"] } webauthn-rs-proto = "=0.6.1-dev" # Security # Direct only so `crypto::install_default_crypto_provider` can name a provider; # every TLS client here reaches rustls transitively. rustls = { version = "0.23", default-features = false, features = ["ring"] } rand = "0.10" hmac = "0.13.0" sha1 = "0.11.0" sha2 = "0.11.0" subtle = "2.6" chacha20poly1305 = "0.11.0" hex = "0.4.3" base64 = "0.22.1" # SSHSIG verification for signed commits and tags (`git::signing`). Feature set # matches `validation::users::SSH_KEY_TYPES`, the algorithms MNW accepts as # account keys: there is no point verifying a signature by a key nobody could # have registered. 0.6 rather than the 0.7 release candidate, and RustCrypto # rather than a C library, per the crypto-provider preference. ssh-key = { version = "0.6", default-features = false, features = ["alloc", "std", "ed25519", "p256", "p384", "p521", "rsa"] } # Temp files (content export) tempfile = "3" # File scanning infer = "0.22" goblin = "0.10" zip = "8.2" # Single-stream decompressors for archive-bomb detection (already in the tree # transitively via `zip`; pinned to the resolved versions so no new build). flate2 = "1" bzip2 = "0.6" xz2 = "0.1" zstd = "0.13" yara-x = "1.16" fs2 = "0.4" memmap2 = "0.9" # CSV parsing (import system) csv = "1.3" # CLI clap = { version = "4", features = ["derive"] } # Logging (used by sqlx slow query config) log = "0.4" # Error handling thiserror = "2.0.18" anyhow = "1.0.102" bytes = "1" # Email validation (used at notify-me signup and guest-checkout entry points) email_address = "0.2" # Metrics metrics = "0.24" metrics-exporter-prometheus = { version = "0.18.1", default-features = false } # Markdown rendering + documentation engine docengine = { git = "https://makenot.work/git/max/docengine.git", version = "0.7", features = ["doc-loader", "directives", "frontmatter", "media-urls"] } mnw-assumptions = { path = "../shared/mnw-assumptions" } # Tag standard tagtree = { path = "../shared/tagtree" } # The git-over-SSH command grammar, shared with mnw-cli so the two SSH doors # cannot drift apart the way their hand-written parsers did. git-command = { path = "../shared/git-command" } # Custom Pages HTML/CSS sanitization. Extracted from `src/custom_pages/` # 2026-08-25 (infra `3e6154ea`) so its fuzz targets do not have to build the # whole server, which is what made them targets nobody ran. It imports nothing # from here: the policy goes in, sanitized output and rejections come out. custom-pages = { path = "../shared/custom-pages" } # Shared theme palette + the bundled theme set (Tier 0 creator theming). makeover = "3.1" # The description layer, for the tier G1 spike only: one read-only screen served # through quasi beside its Askama equivalent, so the cost of describing a screen # here is measured rather than argued. Not load-bearing for any shipped route. # See wiki look-wave-2, tier G. quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.99" } # The description vocabulary quasi's screen types are built from. Pinned here # rather than reached through quasi-router's re-export because a described # screen names FieldKind and Tone directly; it has to track what quasi-router # resolves or the two `layout::` paths are different crates. makeover-layout = "0.44" # The webview renderer's field emitter, called directly by # `quasi::rich_field` so one markdown editor's markup comes from the same # place a whole described screen's would. Pinned here rather than reached # through quasi-webview for makeover-layout's reason: two `form::` paths from # two resolutions are two crates, and the `data-format="markdown"` rules in # `static/layout.css` are generated from this one. makeover-webview = "0.73" # For the request head the per-viewer state factory reads. axum re-exports it, # but the factory's signature is quasi-axum's and names `http::request::Parts`. http = "1.3.1" quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.99" } quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.99" } # The first-party widget set. The carousel was proved here against three pages # and then moved there; what is left in `quasi/widgets/carousel.rs` is the # Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it # sits above quasi-router rather than inside the description suite. quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.99" } # Git source browser gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] } syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "html", "regex-fancy"] } regex = "1" semver = "1" # S3 Storage s3-storage = { path = "../shared/s3-storage" } # Stripe Payments: async-stripe 1.0.0-rc.6 (split into sub-crates). # The umbrella `async-stripe` provides the HTTP client; resource types live in # per-domain sub-crates. The `deserialize` feature on each resource crate is # required to derive `serde::Deserialize` on Subscription, Invoice, etc: we # parse them from webhook payloads ourselves (no built-in webhook helper in the # rc line; see `payments::webhooks::verify_signature` for our HMAC check). # `default-tls` is native-tls, which drags openssl-sys in through hyper-tls; # cargo-deny bans it. rustls-tls-native keeps the system trust store (matching # reqwest's platform verifier elsewhere in the tree) and carries the `__hyper` # feature the client needs; the provider must be named separately or # async-stripe's connector.rs refuses to compile. async-stripe = { version = "1.0.0-rc.6", default-features = false, features = ["rustls-tls-native", "rustls-ring"] } async-stripe-shared = { version = "1.0.0-rc.6", features = ["deserialize"] } async-stripe-billing = { version = "1.0.0-rc.6", features = ["deserialize", "subscription", "billing_portal_session"] } async-stripe-checkout = { version = "1.0.0-rc.6", features = ["deserialize", "checkout_session"] } async-stripe-connect = { version = "1.0.0-rc.6", features = ["deserialize", "account", "account_link", "transfer", "transfer_reversal"] } async-stripe-core = { version = "1.0.0-rc.6", features = ["deserialize", "balance", "refund", "customer"] } async-stripe-product = { version = "1.0.0-rc.6", features = ["deserialize", "product", "price"] } async-stripe-types = { version = "1.0.0-rc.6", features = ["deserialize"] } # reqwest 0.13's `rustls` feature is an alias for `__rustls-aws-lc-rs` and there # is no ring counterpart, so the default feature set is what would drag the C # backend back in. `rustls-no-provider` keeps the platform verifier and lets # `crypto::install_default_crypto_provider` supply ring; the rest of this list is # reqwest's own default set (`charset`, `http2`, `system-proxy`) restated, since # naming a TLS feature means turning defaults off. reqwest = { version = "0.13", default-features = false, features = [ "json", "cookies", "form", "query", "charset", "http2", "system-proxy", "rustls-no-provider", ] } urlencoding = "2.1.3" # URL parsing url = "2.5.8" apple-codesign = { version = "0.29.0", default-features = false } authenticode = { version = "0.6.0", features = ["std", "object"] } x509-cert = "0.2.5" const-oid = { version = "0.9", features = ["db"] } object = { version = "0.39", features = ["pe"] } [[bin]] name = "mnw-admin" path = "src/bin/mnw-admin.rs" # Writes the OpenAPI spec to openapi.json. The spec is the SyncKit wire # contract and the client is in another repo, so it ships as a committed # artifact rather than only as a served endpoint. [[bin]] name = "export-openapi" path = "src/bin/export-openapi.rs" [[bin]] name = "export-operational-mail-doc" path = "src/bin/export-operational-mail-doc.rs" [build-dependencies] # Emits static/geometry.css (makeover-geometry) and static/layout.css # (makeover-webview). The same generator GO and BB run; only the output paths # differ, since the server serves its stylesheets rather than bundling them. makeover-build = "0.66" # Read directly for the embeds' own copy of the spacing layer, which they need # because an iframe cannot link a stylesheet. makeover-build does not re-export # it, so the pin lives here and has to track the one makeover-build resolves. makeover-geometry = "0.7" # Cuts the two house faces into `static/fonts/` at build time. It is a git # dependency because quasi-type is `publish = false`, which is also why # makeover-build cannot do this and only emits the CSS that fetches the result. # # The rev is pinned, and it is the same rev shop's `shop-font` and Alloy's # `QUASI_TYPE_REV` name. That is the point of pinning it here: the house face is # one face, so the one a browser downloads has to be the one the image installs # and the terminal bundles. The three move together, in one pass, or a user sees # two Quasi Monos. Under the tree's `[patch]` block the rev is ignored and the # working copy is what builds. quasi-type = { git = "https://makenot.work/git/max/quasi-type.git", version = "0.1.3", rev = "f5e9e9c96f32ec5e8c5b74089e6ec436c936e0b8" } # Writes the two scripts the renderer ships into `static/`, from the constants # the crate carries. They are the renderer's behaviour rather than the app's -- # a bulk bar's count, a readout that keeps ticking -- and the shell has always # named their addresses, so without this the head links two files that 404. # Written from the crate rather than copied into the repo for the reason the # constants exist: a stale copy in a static directory is a silently dead bulk # bar, and the version has to be the one the emitter agrees with. quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.99" } [dev-dependencies] tower = { version = "0.5.3", features = ["util"] } http-body-util = "0.1" webauthn-authenticator-rs = { version = "=0.6.1-dev", features = ["softpasskey"] } tempfile = "3" proptest = "1" wiremock = "0.6" pom-contract = { path = "../shared/pom-contract" } # The parity harness's normalizer. Already in the graph as ammonia's parser -- # reached through `custom-pages` since the sanitizers moved out -- so this pins # the same build rather than adding one; a dev-dependency because nothing in the # served binary parses HTML. html5ever = "0.39" [profile.release] # Drop the symbol table from the shipped binary. Release builds already carry no # debuginfo, so this is purely the symbol table: measured 161.3 MiB -> 120.7 MiB # on 0.10.19 (25.1%) for no runtime cost. Every tier rsyncs that difference on # every promote. Nothing here reads a symbolized backtrace (no `backtrace` crate, # no RUST_BACKTRACE in the unit file). If a panic ever does need symbolizing, # rebuild the sha with `--config profile.release.strip=false`: the build is # reproducible from the git sha the release was cut from. strip = true [lints.rust] unused = "warn" unreachable_pub = "warn" [lints.clippy] pedantic = { level = "warn", priority = -1 } # Allow-list tuned from a measured breakdown across server/multithreaded/pter # (2026-07-22). These are the high-churn / low-signal pedantic lints; everything # else in `pedantic` stays a warning. Keep this block identical across repos. module_name_repetitions = "allow" # Doc lints. No docs-completeness push is underway. missing_errors_doc = "allow" missing_panics_doc = "allow" doc_markdown = "allow" # Numeric casts. Endemic and mostly intentional in size and byte math. cast_possible_truncation = "allow" cast_sign_loss = "allow" cast_precision_loss = "allow" cast_possible_wrap = "allow" cast_lossless = "allow" # Subjective structure and style nags. High churn, low signal. must_use_candidate = "allow" too_many_lines = "allow" struct_excessive_bools = "allow" similar_names = "allow" items_after_statements = "allow" single_match_else = "allow" # Frequent false-positives in TUI and router-heavy code. match_same_arms = "allow" unnecessary_wraps = "allow" type_complexity = "allow"