feat: per-key spending-rule enforcement for Lambda parity (PRs 1-3) - #477
Open
clawdbot-glitch003 wants to merge 12 commits into
Open
clawdbot-glitch003 wants to merge 12 commits into
clawdbot-glitch003 wants to merge 12 commits into
Conversation
… PR 1) First slice of the Lambda-parity work (plans/chipotle-lambda-parity.md): the durable home for per-key blast-radius controls the gateway will enforce. - Migration: spending_rules (rolling spend cap+window, rate rps+burst, concurrency, origin allowlist, enabled) and spending_usage (rolling spend counter with in-SQL window reset). - New `spending` module: typed rows + validated upsert request, sqlx db layer (upsert/get/list/delete rules, atomic record_charge with window reset), ServiceAuth bearer guard for gateway calls, and routes. - Operator-authed CRUD under /api/spending-rules (admin UI) and ServiceAuth-authed /internal endpoints (gateway: fetch rules for its cache, record spend off the response path). - INTERNAL_SERVICE_TOKEN config; /internal endpoints disabled (503) when unset. No production behavior change — nothing reads these tables yet. 9 unit tests cover request validation, hash canonicalization, and the token comparison. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…parity PR 3) The on-chain zero-latency gate for per-key spending rules. All additive and storage-safe: a new mapping appended to the END of root AccountConfigStorage (not a field on the inline-embedded UsageApiKey struct, which would shift the Account layout). - AppStorage: append `mapping(uint256 => bool) usageKeyHasSpendingRules`. - ViewsFacet: `getSpendingRulesFlag(apiKeyHash)` and `canExecuteActionWithSpendingRules(apiKeyHash, cidHash) -> (canExecute, hasSpendingRules)` so the gateway reads both in one RPC. - WritesFacet: `setSpendingRulesFlag(accountApiKeyHash, usageApiKeyHash, bool)` + event, same account-access control as setUsageApiKey. Source only; `forge build` passes. Regenerate the Rust bindings + diamond ABI with `make generate` on the canonical toolchain, then deploy via Safe diamond-cut on Base (both ops steps). The gateway reads the new view through a scoped sol! interface, so it does not depend on the regenerated giant binding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(lambda-parity PR 2) Gateway side of the Lambda-parity work. Reads the on-chain hasSpendingRules gate in the same multicall as the execute-permission check; keys without rules pay zero extra work. - accounts: can_execute_action_with_spending_rules -> (can_execute, has_spending_rules), cached in BlockchainCache (new execute_and_spending entry, same generation invalidation). Reads the new view via a scoped sol! interface (read_only_client_and_address helper) so it tracks the workspace alloy version instead of the regenerated giant binding. - core::spending_rules: flag-gated enforcer. On a flagged key it fetches rules (cached, TTL) from lit-payments /internal, then enforces a rolling spend cap (402), a per-node token-bucket rate limit (429), and a per-node concurrency cap (429). Spend is recorded off the response path (in-memory + fire-and- forget POST). Inert unless LIT_PAYMENTS_INTERNAL_URL + INTERNAL_SERVICE_TOKEN are set AND the key's on-chain flag is on. - Wired into core_features::lit_action + the /lit_action route + main state. - 429 (too_many_requests) ApiStatus helper. Origin allowlist (P2.1) deferred. SWR background refresh for the rules cache is a marked follow-up (currently TTL with inline cold-miss fetch). cargo check clean; 4 unit tests for the token bucket, window reset, and hash format. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ty-payments-storage # Conflicts: # lit-api-server/src/accounts/signable_contract.rs # lit-api-server/src/main.rs # lit-payments/src/config.rs # lit-payments/src/main.rs
Brings the two design plans from PR #446 into this branch so they ship with the lambda-parity implementation they describe, and #446 can be closed. - plans/chipotle-lambda-parity.md — prioritized gateway work (this PR implements P0.0/P0.1/P0.2/P1) for per-key blast-radius parity with a public Lambda URL. - plans/private-apps-backend.md — the backend-as-Lit-Actions framework these gateway controls unlock (relay becomes optional). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-477-remaining-work # Conflicts: # lit-api-server/blockchain/lit_node_express/contracts/AccountConfigFacets/AppStorage.sol
…ED_SECRET for /internal The five hand-built test Configs were missing the new internal_service_token field. Also let the spending /internal endpoints fall back to the LIT_INTERNAL_SHARED_SECRET that lit-api-server and lit-payments already share, so enabling spending rules needs no second secret. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…mit (P0.2) Gateway enforcement for flagged keys now also checks: - allowed_origins: the browser Origin must match an entry (scheme://host[:port], leading *. wildcard on host). Missing Origin -> 403. - ip_rate_limit_rps/burst: a second token bucket keyed by (key, client IP), using Rocket's client_ip (X-Real-IP behind the ingress; see guards::rate_limit for the trust model). Per-IP buckets are swept when idle past 1h. Both are read from the same lit-payments row (new columns + validation). A SpendingContext request guard carries Origin + client IP into admit(). /lit_binary_action now runs the same combined execute+spending check and admission as /lit_action so a frontend key can't sidestep its rules there. Fixes the post-merge compile break (can_execute_action import). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the on-chain flag Master-key-authed account-management endpoints close the loop the PR body listed as unwired: - POST /set_spending_rules -> PUT rules to lit-payments (/internal, service auth), then setSpendingRulesFlag(account, usage, true). Ownership is proven by the contract (NoAccountAccess revert) before any row is written for an already-flagged key, and by the flag tx otherwise. - POST /remove_spending_rules -> flag false, then delete row + usage counter. - GET /get_spending_rules -> rules + window spend + on_chain_flag (exposes a half-failed write for reconciliation). setSpendingRulesFlag/getSpendingRulesFlag are called via the scoped sol! interface (no binding regen needed). Both key caches are invalidated on write. lit-payments gains ServiceAuth PUT/DELETE /internal/spending-rules/<hash>. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… spending-rules calls CI's contract-bindings check requires the generated binding to match the facets, so regenerate it (forge 1.5.1, make generate). With the new functions in the binding, drop the scoped sol! interface and the address helper and call canExecuteActionWithSpendingRules / getSpendingRulesFlag / setSpendingRulesFlag through the regular AccountConfig instance like every other call. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ndpoints Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…_rules migration - docker-compose.phala.yml reads LIT_PAYMENTS_INTERNAL_URL; deploy-staging and manual_phala-envs-update pass vars.LIT_PAYMENTS_STAGING_URL (the same lit-payments the next/dev dashboards already talk to). Prod workflows are untouched: unset -> the feature stays disabled there. - Migration renumbered 20260604 -> 20260915 so it sorts after everything already applied on the staging database (it has never been applied anywhere). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the Chipotle gateway controls that make a usage API key safe to embed in a frontend, with blast-radius parity to a public AWS Lambda URL (see
plans/chipotle-lambda-parity.md). One branch, logical layers as separate commits. Now merged withmain(storage layout: the deployedpkpIdToOwnerMastermapping stays first,usageKeyHasSpendingRulesis appended after it).PR 1 — lit-payments storage. New
spending_rules+spending_usagetables (migration) and aspendingmodule: Operator-authed CRUD under/api/spending-rulesfor the admin UI, andServiceAuth-authed/internalendpoints for the gateway to fetch rules, record rolling spend, and (new) write/clear rules on behalf of an account owner. Disabled (503) unless a token is set — acceptsINTERNAL_SERVICE_TOKEN, falling back to the existingLIT_INTERNAL_SHARED_SECRET, so no second secret is required.PR 3 — AccountConfig contract. Adds the on-chain
hasSpendingRulesgate: a new mapping appended to the end of root storage (storage-safe), asetSpendingRulesFlagwriter, andcanExecuteActionWithSpendingRules/getSpendingRulesFlagviews. Rust bindings are regenerated (make generate, forge 1.5.1) and the server calls the new functions through the normalAccountConfiginstance. The diamond-cut deploy of the new Views/Writes facets is the remaining ops step.PR 2 — gateway enforcement. Reads the flag in the same multicall as the execute check (cached in
BlockchainCache), so keys without rules pay zero extra work. For a flagged key it enforces, in order:Originmust match an entry (scheme://host[:port], leading*.host wildcard); missing/unlisted → 403,client_ip(X-Real-IPbehind the ingress; seeguards::rate_limitfor the trust model), idle buckets swept (429),recording spend off the response path. Both
/lit_actionand/lit_binary_actiongo through the same check so a frontend key can't sidestep its rules via the binary runner. Inert unlessLIT_PAYMENTS_INTERNAL_URL+ a token are set and the key's flag is on.PR 4 — owner API wires DB → on-chain flag. Master-key-authed account-management endpoints:
POST /set_spending_rules— stores the row in lit-payments, thensetSpendingRulesFlag(account, usage, true)(ownership proven by the contract'sNoAccountAccessrevert),POST /remove_spending_rules— flag off, then delete row + usage counter,GET /get_spending_rules?usage_api_key=— rules, current-window spend, andon_chain_flag(surfaces a half-failed write for reconciliation).k6 client regenerated for the new endpoints.
cargo clippy --testsis clean for both crates; 360 lit-api-server and 151 lit-payments unit tests pass (incl. new origin-matching, per-IP bucket, and admit-path tests).Still not in this PR (ops / follow-ups): Safe diamond-cut of the new facets (until then
set_spending_rulesreverts with an unknown-selector error and the gateway's view call fails → cached as "no rules", i.e. fail-open); SWR background refresh for the rules cache (TTL + inline cold-miss fetch today); dashboard UI for the new endpoints (P2.2); spend-cap auto-disable; verifying the dstack ingress sets a trustworthyX-Real-IPso per-IP limits are per-client rather than global.🤖 Generated with Claude Code