feat(backend): agentflare-backend CRUD/webhooks/MCP tools + agentflare-db-kit - #162
Conversation
…lare-db-kit shared crate Ports Plane's workspace/project/item domain model into a new agentflare-backend crate (schema, CRUD, state machine, webhook delivery) and exposes it as backend_* MCP tools on AgentflareMcp. Workspace and project are fully automatic: one workspace per system, auto-created on first use; the current repo auto-links to a project via .agentflare/project.json (Vercel-style), recovering by repo identity (git remote, or canonicalized path) rather than derived name if the link file is lost, so two differently-located repos that happen to share a directory name are never conflated into one project. Also factors the connection-open/migration boilerplate and the leased-claim upsert pattern duplicated across src/claims.rs and this new crate into a shared agentflare-db-kit crate (open_file/open_memory via rusqlite_migration, ids::now/new_id, a generic ClaimLedger over composite or single-column keys), and retrofits src/claims.rs onto it.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughAdds two Rust crates: a SQLite-backed Agentflare backend and shared database utilities. The backend provides persistence, workflows, assets, webhooks, and MCP tools. Existing claim handling now delegates lease operations to the shared claim ledger. ChangesAgentflare backend
Shared database kit and claims
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant MCP
participant ProjectLink
participant BackendDB
participant Backend
participant WebhookEndpoint
MCP->>ProjectLink: Resolve repository project
MCP->>BackendDB: Open or reuse backend connection
MCP->>Backend: Create or update work item
Backend->>BackendDB: Persist item and relationships
Backend->>WebhookEndpoint: Deliver matching event
Backend->>BackendDB: Record delivery log
Backend-->>MCP: Return backend result
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (1)
src/mcp_server.rs (1)
1082-1100: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftBackend DB lock spans git and webhook POSTs
with_backend_dbholds the mutex for the entire closure, so the backend calls that go through it serialize whileresolve_project()shells out togitandevents::emit()delivers webhooks synchronously with a 5s timeout. Move webhook fan-out off the request path, or at least out of the critical section, if backend throughput matters.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/mcp_server.rs` around lines 1082 - 1100, Reduce mutex contention in with_backend_db by ensuring its lock is not held while callers perform slow git operations or synchronous webhook delivery. Refactor the affected backend call paths, including resolve_project and events::emit usage, to copy or access the needed database state under the lock, then release it before running external commands or webhook fan-out; preserve existing results and error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/agentflare-backend/src/asset.rs`:
- Around line 41-55: Harden storage_path and every filesystem helper, including
write_file and the related read/delete helpers, against traversal by rejecting
absolute paths and any non-normal components in workspace_id or filename.
Resolve the resulting asset path beneath a non-symlinked base_path, verify
containment before each filesystem operation, and reject symlink escapes rather
than reading, writing, or deleting outside the asset root.
In `@crates/agentflare-backend/src/item.rs`:
- Around line 208-211: Before inserting in the item creation flow, validate any
provided input.state_id against the current project using the same
project-membership check as update_state(). Reuse that existing validation logic
or helper, and reject mismatched states before item::create proceeds; leave
creation unchanged when state_id is absent.
In `@crates/agentflare-backend/src/state.rs`:
- Around line 195-204: Update states::delete to prevent deleting the active
default state unless a replacement default is designated atomically in the same
operation. Preserve the existing NotFound behavior for missing or
already-deleted states, and ensure MCP item creation without state_id can still
resolve a default after deletion.
In `@crates/agentflare-backend/src/webhook.rs`:
- Around line 8-21: Update the Webhook struct’s serialization behavior so
secret_key is never included in serialized create, get, or list responses.
Prefer a redacted response DTO if the existing API supports one; otherwise
configure secret_key to be skipped during serialization while preserving its
ability to deserialize and use the signing secret internally.
- Around line 298-309: Update the response-body handling in the webhook delivery
flow around request.send_bytes and log_delivery to cap the body before storing
it in webhook_logs.response_body. Apply the same bounded-prefix behavior to
successful responses and HTTP status errors, and mark the stored value when
truncation occurs; avoid retaining or logging the full remote response.
- Around line 81-100: Harden validate_webhook_url and the webhook delivery flow
to reject private, link-local, loopback, and other non-public connection
addresses, including targets resolved from hostnames. Resolve the hostname and
re-check the actual peer address immediately before sending, and validate every
redirect destination before following it so existing stored webhook URLs receive
the same protection.
In `@crates/agentflare-db-kit/src/claim.rs`:
- Around line 91-122: Update the claim operation around the UPSERT and follow-up
result handling to derive Acquire directly from the write, using SQLite
RETURNING or a transaction that makes the write and read atomic. Ensure a
successful claim cannot be changed by concurrent release/done activity before
determining whether to return Acquired or Held, while preserving the existing
owner and age_secs values.
In `@src/claims.rs`:
- Around line 7-16: Update the acquire flow around LEDGER.acquire() and the
subsequent git_commit UPDATE so provenance is written only to the lease
acquisition that succeeded. At minimum, add the acquired owner to the UPDATE
predicate; preferably perform acquisition and provenance update in one
transaction or use a lease generation/token to bind the UPDATE to the exact
acquisition, preventing a stolen lease from receiving the previous owner’s
git_commit.
In `@src/mcp_server.rs`:
- Around line 2236-2268: Add upfront validation in both backend_item_add_label
and backend_item_remove_label to reject blank item_id or label_id values with
the established invalid_params response used by other backend tools. Perform the
checks before with_backend_db, while preserving the existing database operations
and success responses for valid IDs.
- Around line 2270-2301: Update backend_webhook_create to validate url with
validate_webhook_url before creating or persisting the webhook, expanding
validation to reject RFC1918, 169.254.0.0/16, IPv6 link-local and ULA addresses,
plus hostnames resolving to any of those ranges. Return invalid parameters for
blocked targets and preserve the existing project resolution and webhook
creation flow for allowed URLs.
---
Nitpick comments:
In `@src/mcp_server.rs`:
- Around line 1082-1100: Reduce mutex contention in with_backend_db by ensuring
its lock is not held while callers perform slow git operations or synchronous
webhook delivery. Refactor the affected backend call paths, including
resolve_project and events::emit usage, to copy or access the needed database
state under the lock, then release it before running external commands or
webhook fan-out; preserve existing results and error handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 95914542-b368-4751-a3e9-5b00962aedb2
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (24)
.gitignoreCargo.tomlcrates/agentflare-backend/CHANGELOG.mdcrates/agentflare-backend/Cargo.tomlcrates/agentflare-backend/src/asset.rscrates/agentflare-backend/src/db.rscrates/agentflare-backend/src/error.rscrates/agentflare-backend/src/events.rscrates/agentflare-backend/src/item.rscrates/agentflare-backend/src/label.rscrates/agentflare-backend/src/lib.rscrates/agentflare-backend/src/project.rscrates/agentflare-backend/src/schema.sqlcrates/agentflare-backend/src/state.rscrates/agentflare-backend/src/webhook.rscrates/agentflare-backend/src/workspace.rscrates/agentflare-db-kit/CHANGELOG.mdcrates/agentflare-db-kit/Cargo.tomlcrates/agentflare-db-kit/src/claim.rscrates/agentflare-db-kit/src/ids.rscrates/agentflare-db-kit/src/lib.rscrates/agentflare-db-kit/src/open.rssrc/claims.rssrc/mcp_server.rs
| #[tool( | ||
| description = "Register a webhook that fires on item/state/project changes in the repo's linked workspace. secret is auto-generated if omitted — save the returned value, it isn't shown again." | ||
| )] | ||
| fn backend_webhook_create( | ||
| &self, | ||
| Parameters(BackendWebhookCreateRequest { | ||
| url, | ||
| secret, | ||
| on_item, | ||
| on_state, | ||
| on_project, | ||
| }): Parameters<BackendWebhookCreateRequest>, | ||
| ) -> Result<String, ErrorData> { | ||
| if url.trim().is_empty() { | ||
| return Err(ErrorData::invalid_params("url is required", None)); | ||
| } | ||
| self.with_backend_db(|conn| { | ||
| let project = self.resolve_project(conn)?; | ||
| let secret_key = secret.unwrap_or_else(generate_webhook_secret); | ||
| let input = agentflare_backend::webhook::CreateWebhook { | ||
| workspace_id: project.workspace_id, | ||
| url, | ||
| secret_key, | ||
| on_item, | ||
| on_state, | ||
| on_project, | ||
| }; | ||
| let webhook = | ||
| agentflare_backend::webhook::create(conn, input).map_err(map_backend_err)?; | ||
| Ok(serde_json::to_string_pretty(&webhook).unwrap_or_default()) | ||
| })? | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect webhook creation/delivery for SSRF guards.
rg -nP 'fn (create|deliver|validate)\b' crates/agentflare-backend/src/webhook.rs -A15
rg -nP '169\.254|127\.0\.0\.1|loopback|is_private|is_loopback|localhost' crates/agentflare-backend/src/webhook.rsRepository: getappz/agentflare
Length of output: 1888
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect URL validation and delivery implementation in context.
FILE="crates/agentflare-backend/src/webhook.rs"
# Show the validation function and nearby lines.
sed -n '70,120p' "$FILE"
# Show the delivery implementation where the outbound request is made.
sed -n '271,360p' "$FILE"
# Find any other URL/IP validation helpers in the webhook module.
rg -n 'is_private|is_loopback|localhost|169\.254|127\.0\.0\.1|0\.0\.0\.0|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.' "$FILE"Repository: getappz/agentflare
Length of output: 5435
Block private and link-local webhook targets. validate_webhook_url only rejects localhost/loopback today; add checks for RFC1918, 169.254.0.0/16, IPv6 link-local/ULA, and hostnames that resolve there before persisting, or webhook deliveries can still reach internal services.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/mcp_server.rs` around lines 2270 - 2301, Update backend_webhook_create to
validate url with validate_webhook_url before creating or persisting the
webhook, expanding validation to reject RFC1918, 169.254.0.0/16, IPv6 link-local
and ULA addresses, plus hostnames resolving to any of those ranges. Return
invalid parameters for blocked targets and preserve the existing project
resolution and webhook creation flow for allowed URLs.
…l ~/.agentflare dir Non-git directories had no stable "project root" — repo_root() fell back to raw cwd, so the same non-git project would silently split into multiple linked projects depending on which subdirectory a tool was called from. Fixes that with a git-like directory walk-up for non-git projects (looks for an existing link, then common project markers). But the walk-up's marker was named .agentflare — the same name as this codebase's existing global per-user data directory (~/.agentflare, holding agentflare.db, artifacts, etc.), which exists on every machine. Since that directory always exists, the walk would eventually find it from almost any non-git location and treat the user's home directory as "the project root," collapsing every non-git project on the machine into one and writing project.json alongside the real global data files. Renames the per-repo link marker to .agentflare-project (no collision with the global dir) and additionally bounds the walk-up so it can never reach the user's home directory at all, regardless of what any marker is named.
…ely on the home boundary Reverts the .agentflare-project rename from the previous commit — the home-directory boundary in find_root_from is sufficient on its own to keep the per-repo link marker from ever colliding with the global ~/.agentflare data dir, since that directory only ever exists at exactly one path (home itself), and the walk now never inspects that path at all. Also fixes a real test-isolation bug this surfaced: find_root_from was calling crate::paths::home() internally, which reads the AGENTFLARE_HOME_OVERRIDE env var other tests mutate concurrently under their own lock. That made the walk-up's result depend on unrelated tests' timing. home is now passed in by the caller (repo_root() supplies the real one; tests supply an isolated one), same principle already applied to start.
- asset.rs: reject path traversal in storage_path (write/read/delete_file) - item.rs: validate state_id belongs to the item's project on create - state.rs: block deleting a project's default state - webhook.rs: redact secret_key from Serialize; reveal it once at creation in mcp_server.rs instead - webhook.rs: harden validate_webhook_url against RFC1918/link-local/ULA ranges; disable HTTP redirects on delivery; cap logged response bodies - agentflare-db-kit claim.rs: acquire() reports its own outcome via RETURNING instead of a racy follow-up SELECT - src/claims.rs: scope the git_commit UPDATE to the acquiring owner - mcp_server.rs: reject blank item_id/label_id on label attach/detach
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/agentflare-backend/src/item.rs (1)
200-268: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPreserve the project/state invariant in
update()
UpdateItem.state_idstill goes straight into the dynamicUPDATE, so this path can move an item to any existing state, including one from another project. Theitems.state_id -> states(id)foreign key does not enforce project ownership; add the same project-membership check used bycreate()/update_state(), or route state changes throughupdate_state(), and cover it with a regression test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/agentflare-backend/src/item.rs` around lines 200 - 268, The update function must prevent UpdateItem.state_id from assigning a state belonging to another project. Before applying the dynamic UPDATE, validate the requested state against the item’s project using the existing project-membership check from create() or update_state(), or delegate the state change through update_state(); reject invalid cross-project states without modifying the item. Add a regression test covering this scenario.
♻️ Duplicate comments (1)
crates/agentflare-backend/src/webhook.rs (1)
116-160: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftSSRF hardening is still bypassable for domain names — hostname is never resolved/re-checked, only literal
"localhost"string and IP literals are blocked.
validate_webhook_urlblocks IP-literal hosts viais_blocked_ipand the literal string"localhost", but any other domain (e.g.internal.attacker.com) passes validation untouched. Atdeliver()time,ureqresolves that domain via DNS and connects to whatever address it points to — including169.254.169.254,10.0.0.1, or a DNS-rebound target that resolves differently between validation and delivery. This is the same class of gap the prior review specifically called out: "Resolve and re-check the actual connection target at send time... DNS-rebound hostnames can slip through too." The redirect-following and IP-literal portions of that feedback are now fixed, but the core recommendation to validate the resolved address at connection time remains unaddressed.To fully close this, resolve the hostname to its IP(s) either at validation time and again immediately before/at send time (or use a custom resolver that runs
is_blocked_ipon the connect address, e.g. viaureq'sResolvertrait), rejecting delivery if any resolved address is blocked.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/agentflare-backend/src/webhook.rs` around lines 116 - 160, Extend validate_webhook_url and the deliver flow to resolve domain hosts and apply is_blocked_ip to every resolved address, both during validation and immediately before connection. Reject the webhook if any address is blocked, and ensure delivery uses the checked resolution or a resolver that revalidates the actual connection target to prevent DNS rebinding.
🧹 Nitpick comments (2)
crates/agentflare-backend/src/webhook.rs (2)
398-422: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winGood added coverage for private/link-local IP rejection.
Consider adding a case for an IPv4-mapped IPv6 literal (
http://[::ffff:127.0.0.1]/) once the bypass above is fixed, to lock in the regression.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/agentflare-backend/src/webhook.rs` around lines 398 - 422, Add an assertion in validate_url_rejects_private_and_link_local for http://[::ffff:127.0.0.1]/, expecting validate_webhook_url to return an error, after fixing the IPv4-mapped IPv6 bypass.
42-56: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
secret_keyno longer serialized — resolves prior secret-leak finding.
#[serde(skip_serializing)]preventssecret_keyfrom being emitted byget/list/createJSON responses, matching the MCP layer's create-time-only reveal pattern.One residual gap:
Webhookstill derivesDebug, andskip_serializinghas no effect on{:?}formatting — any futuretracing::debug!("{:?}", webhook)or panic-message would still print the secret. Consider a manualDebugimpl (or a wrapper) that redactssecret_keyto fully close this off defensively.Example manual Debug redaction
-#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub struct Webhook { pub id: String, pub workspace_id: String, pub url: String, pub is_active: bool, #[serde(skip_serializing)] pub secret_key: String, pub on_item: bool, pub on_state: bool, pub on_project: bool, pub created_at: i64, pub updated_at: i64, pub deleted_at: Option<i64>, } + +impl std::fmt::Debug for Webhook { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Webhook") + .field("id", &self.id) + .field("workspace_id", &self.workspace_id) + .field("url", &self.url) + .field("is_active", &self.is_active) + .field("secret_key", &"[redacted]") + .field("on_item", &self.on_item) + .field("on_state", &self.on_state) + .field("on_project", &self.on_project) + .field("created_at", &self.created_at) + .field("updated_at", &self.updated_at) + .field("deleted_at", &self.deleted_at) + .finish() + } +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/agentflare-backend/src/webhook.rs` around lines 42 - 56, Replace the derived Debug implementation on Webhook with a manual implementation that omits or redacts secret_key while preserving useful fields for diagnostics. Keep serde serialization behavior unchanged, and ensure all Debug formatting of Webhook cannot expose the secret.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/agentflare-backend/src/item.rs`:
- Around line 124-132: Update the item creation flow around state::get and
conn.unchecked_transaction so state validation and item insertion occur within
the same transaction. Begin the transaction before validating the state, and use
an immediate or conditional write strategy that prevents a concurrent
soft-delete from allowing insertion of an inactive state.
In `@crates/agentflare-backend/src/webhook.rs`:
- Around line 129-137: Update the IPv6 handling branch in the IP validation
logic to detect mapped addresses with v6.to_ipv4_mapped(), then re-run the
existing IPv4 validation path for the resulting address. Preserve the current
IPv6 checks for non-mapped addresses so loopback and link-local mapped literals
such as ::ffff:127.0.0.1 and ::ffff:169.254.169.254 are rejected.
---
Outside diff comments:
In `@crates/agentflare-backend/src/item.rs`:
- Around line 200-268: The update function must prevent UpdateItem.state_id from
assigning a state belonging to another project. Before applying the dynamic
UPDATE, validate the requested state against the item’s project using the
existing project-membership check from create() or update_state(), or delegate
the state change through update_state(); reject invalid cross-project states
without modifying the item. Add a regression test covering this scenario.
---
Duplicate comments:
In `@crates/agentflare-backend/src/webhook.rs`:
- Around line 116-160: Extend validate_webhook_url and the deliver flow to
resolve domain hosts and apply is_blocked_ip to every resolved address, both
during validation and immediately before connection. Reject the webhook if any
address is blocked, and ensure delivery uses the checked resolution or a
resolver that revalidates the actual connection target to prevent DNS rebinding.
---
Nitpick comments:
In `@crates/agentflare-backend/src/webhook.rs`:
- Around line 398-422: Add an assertion in
validate_url_rejects_private_and_link_local for http://[::ffff:127.0.0.1]/,
expecting validate_webhook_url to return an error, after fixing the IPv4-mapped
IPv6 bypass.
- Around line 42-56: Replace the derived Debug implementation on Webhook with a
manual implementation that omits or redacts secret_key while preserving useful
fields for diagnostics. Keep serde serialization behavior unchanged, and ensure
all Debug formatting of Webhook cannot expose the secret.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 9016ff60-19cb-4681-83ba-4779bd0eba20
📒 Files selected for processing (7)
crates/agentflare-backend/src/asset.rscrates/agentflare-backend/src/item.rscrates/agentflare-backend/src/state.rscrates/agentflare-backend/src/webhook.rscrates/agentflare-db-kit/src/claim.rssrc/claims.rssrc/mcp_server.rs
🚧 Files skipped from review as they are similar to previous changes (5)
- src/claims.rs
- crates/agentflare-backend/src/state.rs
- src/mcp_server.rs
- crates/agentflare-backend/src/asset.rs
- crates/agentflare-db-kit/src/claim.rs
| let state = crate::state::get(conn, &input.state_id)?; | ||
| if state.project_id != input.project_id { | ||
| return Err(crate::error::Error::InvalidTransition(format!( | ||
| "state {} belongs to a different project than project {}", | ||
| input.state_id, input.project_id | ||
| ))); | ||
| } | ||
|
|
||
| let tx = conn.unchecked_transaction()?; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Make state validation and insertion atomic.
state::get runs before unchecked_transaction(). Since states are soft-deleted, another connection can delete the state after validation but before the item insert. Start the transaction before validation—preferably with an immediate/conditional write—or enforce the active-state predicate in the insert itself.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/agentflare-backend/src/item.rs` around lines 124 - 132, Update the
item creation flow around state::get and conn.unchecked_transaction so state
validation and item insertion occur within the same transaction. Begin the
transaction before validating the state, and use an immediate or conditional
write strategy that prevents a concurrent soft-delete from allowing insertion of
an inactive state.
| std::net::IpAddr::V6(v6) => { | ||
| let seg0 = v6.segments()[0]; | ||
| v6.is_loopback() | ||
| || v6.is_unspecified() | ||
| || v6.is_multicast() | ||
| || (seg0 & 0xffc0) == 0xfe80 // link-local fe80::/10 | ||
| || (seg0 & 0xfe00) == 0xfc00 // unique local fc00::/7 | ||
| } | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target file and nearby context.
git ls-files crates/agentflare-backend/src/webhook.rs
wc -l crates/agentflare-backend/src/webhook.rs
sed -n '1,220p' crates/agentflare-backend/src/webhook.rsRepository: getappz/agentflare
Length of output: 7670
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for related URL/IP validation code and tests.
rg -n "to_ipv4_mapped|is_blocked_ip|validate_webhook_url|Ipv6Addr|Host::Ipv6|localhost|169\.254|127\.0\.0\.1" crates/agentflare-backend/src crates/agentflare-backend -g '!target'Repository: getappz/agentflare
Length of output: 5792
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the existing tests around webhook validation.
sed -n '380,450p' crates/agentflare-backend/src/webhook.rsRepository: getappz/agentflare
Length of output: 2567
Block IPv4-mapped IPv6 literals here.
::ffff:127.0.0.1 and ::ffff:169.254.169.254 still arrive as Host::Ipv6, but this branch never converts them to IPv4, so the loopback/private/link-local checks are skipped. Map them with to_ipv4_mapped() and re-run the IPv4 path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/agentflare-backend/src/webhook.rs` around lines 129 - 137, Update the
IPv6 handling branch in the IP validation logic to detect mapped addresses with
v6.to_ipv4_mapped(), then re-run the existing IPv4 validation path for the
resulting address. Preserve the current IPv6 checks for non-mapped addresses so
loopback and link-local mapped literals such as ::ffff:127.0.0.1 and
::ffff:169.254.169.254 are rejected.
…ict with ci/disable-publish Root Cargo.toml conflicted on the dependency block: master (via #164) had already dropped version pins from internal path deps. Applied the same treatment to the newly added agentflare-backend/db_kit deps, and added publish = false to both crates' own Cargo.toml for consistency.
Summary
agentflare-backendcrate: workspace/project/item/label/state/webhook/asset domain model ported from Plane, full CRUD, soft deletes, per-project sequence IDs, 6-group state machine, synchronous webhook delivery (HMAC-signed, SSRF-guarded).backend_*MCP tools onAgentflareMcp(item create/get/list/update/update_state/delete, label create + attach/detach, webhook create/list/delete, project_info) — all added inside the single existing#[tool_router]-tagged impl block, guarded by a test asserting exactly one such block exists..agentflare/project.json(Vercel-style), derived from the git remote or directory name.agentflare-db-kitshared crate:open_file/open_memory(SQLite connection setup backed byrusqlite_migration),ids::now/ids::new_id, and a genericClaimLedger(composite- or single-column-key leased-claim upsert) factored out of the duplicated boilerplate insrc/claims.rs.src/claims.rsis retrofitted onto it — its public API and all 8 existing tests are unchanged.Test plan
cargo test --workspace— all green (338 in the main binary, 44 inagentflare-backend, 9 inagentflare-db-kit, 0 failures)cargo clippy --workspace --all-targets— cleancargo fmt --check— cleancargo deny check— advisories/bans/licenses/sources all okSummary by CodeRabbit