mirror of
https://github.com/Twigpine/node.git
synced 2026-11-09 05:20:05 -10:00
* feat(gl): sanctioned iCaptcha client flow + secure git lifecycle Builds the client side of the iCaptcha gate so a legitimate agent passes the challenge transparently, with no manual headers and no GITLAWB_ICAPTCHA_PROOF env var. Server enforcement is unchanged. New crate `icaptcha-client` (blocking, shared by gl + the git helper): - deterministic solvers for the computational challenge types (arithmetic, algebra, sequence) matching the iCaptcha generators; anagram/logic/LLM fall back to a solver hook / interactive prompt. - obtain_proof(): POST /v1/challenge (requesterId = caller DID), solve, POST /v1/answer, handle escalation, return the signed proof. Optional bearer auth via GITLAWB_ICAPTCHA_API_KEY. gl (crates/gl): - http.rs: unify post/put/delete through one signed sender that, on a 403 iCaptcha challenge (detected via x-icaptcha-url / x-icaptcha-level headers), solves it and retries the same signed request with the x-icaptcha-proof header (bounded retry absorbs the ~5 min proof TTL). Removes the env hack. Emits an actionable hint on 401 not_an_agent (old-CLI / unregistered). - repo.rs: `repo info` checks status and returns a real 404 instead of a stub card with `?` fields; `repo info`/`clone` resolve the owner from the local identity or an explicit owner/name — never the node's own DID. - doctor.rs: warn on gl-vs-node version drift; add an iCaptcha reachability check. git-remote-gitlawb: same shared client — push stays signed (RFC 9421); on a 403 iCaptcha (safety net; push is signed-only, not gated) it solves and retries. Clone/fetch unsigned; missing repo keeps its clear 404. gitlawb-node: the 403 icaptcha_proof_required response now advertises the service url + required level as JSON fields and x-icaptcha-url / x-icaptcha-level headers (mirroring the human_detected pattern) so clients discover them instead of scraping the message. The sub == authenticated-DID and expiry/level checks are unchanged — enforcement is not weakened. README: documents the full lifecycle (identity -> register -> repo create with auto-iCaptcha -> push -> clone), the requesterId == DID rule, and proof TTL. Tested: solver unit tests; full workspace green against Postgres (node still rejects missing/expired/wrong-subject proofs). * fix(icaptcha-client,gl): address PR #138 review feedback Rebased onto main (resolving the git-remote-gitlawb/main.rs conflict from #119) and addressed the review findings: - [beardthelion P2 / jatmn P2 / CodeRabbit] Removed the dead iCaptcha retry loop from the push path. git_receive_pack never calls verify_request (only create/fork/register are iCaptcha-gated), so a push 403 never carries x-icaptcha-* headers and the loop could never solve. Taking main's push path (via #119) deletes it, which also resolves the pack-clone (retries no longer copy the whole pack) and the interactive-prompt-over-git-stream threads, and restores the sanitized info/refs error path. git-remote-gitlawb no longer depends on icaptcha-client. - [beardthelion P1] Sanitize and length-bound iCaptcha error bodies before they reach the terminal. The origin is only as trusted as the node that advertised it, so its non-2xx bodies (and Failed{reason}) are attacker-influenceable; they went raw into bail! (CWE-150, the #137 class). Now bounded-read + C0/C1-stripped + capped, same shape as #137. - [beardthelion P2] Do not trust or hand credentials to a node-chosen iCaptcha URL. x-icaptcha-url was used as the base with no scheme/host check and the API key was sent to it on the first hop (SSRF + key exfiltration). resolve_solver_url now honors an advertised URL only when https + host-allowlisted (public default or the operator's GITLAWB_ICAPTCHA_URL), else falls back to the trusted origin; the API key is attached only to the operator's own configured origin. - [jatmn P2 / CodeRabbit] gl repo clone with a bare name now derives the short owner key via resolve_owner_did, matching the other commands' gitlawb://z.../name URL shape instead of the colon-bearing full DID. Skipped (with reason): jatmn P3 (doctor GITLAWB_ICAPTCHA_URL) is resolved by the P2 change — that env var is now read by the write path, so the doctor's probe is accurate. CodeRabbit User-Agent-version nit lived in the push code reverted to main. README push wording kept ("signed-only, no per-push challenge") — now accurate since the retry path is gone; the API-key bullet was updated for the new trust model. Tests: added sanitize + resolve_solver_url unit tests; full workspace green. Co-Authored-By: Claude Opus 4.8 <[email protected]> --------- Co-authored-by: Claude Opus 4.8 <[email protected]>
62 lines
1.8 KiB
TOML
62 lines
1.8 KiB
TOML
[workspace]
|
|
resolver = "2"
|
|
members = [
|
|
"crates/gitlawb-core",
|
|
"crates/gitlawb-node",
|
|
"crates/gl",
|
|
"crates/git-remote-gitlawb",
|
|
"crates/gitlawb-attest",
|
|
"crates/icaptcha-client",
|
|
]
|
|
|
|
[workspace.package]
|
|
# Per-crate versions live in each crate's [package] so release-please can manage
|
|
# them (it cannot rewrite an inherited `version.workspace = true`). They are kept
|
|
# in lockstep by release-please's generic updater: each crate's `version` line
|
|
# carries an `# x-release-please-version` annotation and is listed under
|
|
# `extra-files` in release-please-config.json. The release type is `simple`
|
|
# (not `rust`) because the rust strategy unconditionally tries to rewrite this
|
|
# virtual workspace root as a package manifest and errors out.
|
|
edition = "2021"
|
|
rust-version = "1.91"
|
|
license = "MIT OR Apache-2.0"
|
|
authors = ["gitlawb contributors"]
|
|
repository = "https://github.com/gitlawb/node"
|
|
|
|
[workspace.dependencies]
|
|
# async
|
|
tokio = { version = "1", features = ["full"] }
|
|
# serialization
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
serde_jcs = "0.2"
|
|
# errors
|
|
thiserror = "2"
|
|
anyhow = "1"
|
|
# crypto
|
|
ed25519-dalek = { version = "2", features = ["rand_core", "pkcs8", "pem"] }
|
|
rand = "0.8.6"
|
|
sha2 = "0.10"
|
|
# encoding
|
|
hex = "0.4.3"
|
|
base64 = "0.22"
|
|
multibase = "0.9"
|
|
multihash = { version = "0.19", features = ["std"] }
|
|
multihash-codetable = { version = "0.1", features = ["sha2"] }
|
|
cid = "0.11"
|
|
# logging
|
|
tracing = "0.1"
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|
# time
|
|
chrono = { version = "0.4", features = ["serde"] }
|
|
# uuid
|
|
uuid = { version = "1", features = ["v4"] }
|
|
# http client
|
|
reqwest = { version = "0.12", features = ["blocking", "json", "multipart", "rustls-tls"], default-features = false }
|
|
# HMAC
|
|
hmac = "0.12"
|
|
|
|
[profile.release]
|
|
lto = "thin"
|
|
strip = "symbols"
|