[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: discussion #560 register.
Kind: bug. Source: new finding, register row C38 (related to C15 and C16: one HTTP pacing and retry policy).
Problem
utils::concurrent is meant to be the one pacing policy for patch-API fan-out:
- the public proxy gets
PROXY_API_CONCURRENCY = 4, because it "serializes anonymous callers behind one shared server-side semaphore — stay polite there";
SOCKET_API_CONCURRENCY is the operator's escape hatch "for an endpoint that caps in-flight requests per client", and on the proxy it "can only LOWER the cap".
See utils/concurrent.rs#L48-L59 and #L85-L106.
Every CLI window goes through it, including scan's batch windows, discovery, get, vendor, the hosted views, and even the vex record fetch and the hosted wheel-metadata window, which both document "SOCKET_API_CONCURRENCY applies here like every other patch-API window" (vex_sources.rs#L111-L120, [`scan/hosted.rs#L45-L56`](https://github.com/SocketDev/socket-patch/blob/045d7ec783d788bf3c5a1310724b51e09fb6505d/crates/socket-patch-cli/src/commands/scan/hosted.rs#L45-L56)).``
The API client's own per-package fallback doesn't. It has a private constant and semaphore:
search_patches_batch takes this path on the public proxy whenever POST /patch/batch is missing or rejects a chunk's validation (#L744-L755). One exotic PURL in a chunk is enough to trigger it on a current proxy.
Reproduced twice on 045d7ec with a temporary test beside proxy_batch_path_cap_tests (not committed):
- wiremock answers
POST /patch/batch with a 400, and GET /patch/by-package/* after 300 ms;
- the client is on the public proxy,
SOCKET_API_CONCURRENCY=1, and one search_patches_batch call has 10 PURLs.
Output: api_concurrency(proxy)=1 api_concurrency_for(proxy,10)=1 peak_by_package_in_flight=10. Without the override, the policy says 4 and this path still runs 10. The existing test concurrent_batches_share_the_legacy_fallback_cap pins the peak at exactly 10.
A related leftover in the same module: registry_concurrency() (concurrent.rs#L75-L83) has no caller at all, so its documented registry cap and tight-RLIMIT_NOFILE rule apply nowhere.
Symptoms
No existing issue. An operator behind a per-client in-flight limit (WAF, corporate proxy) who sets SOCKET_API_CONCURRENCY=1 still gets 10 parallel GETs on this path, and those can 429 or be dropped. Under a tight fd limit, the api_concurrency rule that forces 1 is bypassed too.
Impact
Medium. The proxy fallback is live for any chunk the batch validator rejects, and it is the one patch-API window that the shared knob doesn't reach. The fix is small and removes a duplicate pacing constant.
Proposed change
- Size
proxy_batch_slots and the chunking from utils::concurrent instead of PROXY_BATCH_PATH_CONCURRENCY. Read the policy at client construction with api_concurrency(use_public_proxy), so the env override and the fd-limit rule apply; for the proxy that is 4, or lower when the override asks.
- Delete
PROXY_BATCH_PATH_CONCURRENCY.
- Delete
registry_concurrency() and REGISTRY_CONCURRENCY, or wire them into the pristine-registry fetch they describe. Deleting is preferred unless a caller exists by then.
Size and scope
api/client.rs (constant, constructor, fallback loop, test) and utils/concurrent.rs: about 20 production lines, plus an updated test. Out of scope: unifying the retry systems (C15) and batch-size limits (C16).
Acceptance criteria
Dependencies
Touches api/client.rs, which open PRs #607 and #610 also edit, in different functions. Blocks nothing.
[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: discussion #560 register.
Kind: bug. Source: new finding, register row C38 (related to C15 and C16: one HTTP pacing and retry policy).
Problem
utils::concurrentis meant to be the one pacing policy for patch-API fan-out:PROXY_API_CONCURRENCY = 4, because it "serializes anonymous callers behind one shared server-side semaphore — stay polite there";SOCKET_API_CONCURRENCYis the operator's escape hatch "for an endpoint that caps in-flight requests per client", and on the proxy it "can only LOWER the cap".See
utils/concurrent.rs#L48-L59and#L85-L106.Every CLI window goes through it, including scan's batch windows, discovery, get, vendor, the hosted views, and even the vex record fetch and the hosted wheel-metadata window, which both document "
SOCKET_API_CONCURRENCYapplies here like every other patch-API window" (vex_sources.rs#L111-L120,[`scan/hosted.rs#L45-L56`](https://github.com/SocketDev/socket-patch/blob/045d7ec783d788bf3c5a1310724b51e09fb6505d/crates/socket-patch-cli/src/commands/scan/hosted.rs#L45-L56)).``The API client's own per-package fallback doesn't. It has a private constant and semaphore:
api/client.rs#L248-L252:const PROXY_BATCH_PATH_CONCURRENCY: usize = 10;#L403:Semaphore::new(PROXY_BATCH_PATH_CONCURRENCY)#L929-L940: chunks of 10, all spawned at once.search_patches_batchtakes this path on the public proxy wheneverPOST /patch/batchis missing or rejects a chunk's validation (#L744-L755). One exotic PURL in a chunk is enough to trigger it on a current proxy.Reproduced twice on
045d7ecwith a temporary test besideproxy_batch_path_cap_tests(not committed):POST /patch/batchwith a 400, andGET /patch/by-package/*after 300 ms;SOCKET_API_CONCURRENCY=1, and onesearch_patches_batchcall has 10 PURLs.Output:
api_concurrency(proxy)=1 api_concurrency_for(proxy,10)=1 peak_by_package_in_flight=10. Without the override, the policy says 4 and this path still runs 10. The existing testconcurrent_batches_share_the_legacy_fallback_cappins the peak at exactly 10.A related leftover in the same module:
registry_concurrency()(concurrent.rs#L75-L83) has no caller at all, so its documented registry cap and tight-RLIMIT_NOFILErule apply nowhere.Symptoms
No existing issue. An operator behind a per-client in-flight limit (WAF, corporate proxy) who sets
SOCKET_API_CONCURRENCY=1still gets 10 parallel GETs on this path, and those can 429 or be dropped. Under a tight fd limit, theapi_concurrencyrule that forces 1 is bypassed too.Impact
Medium. The proxy fallback is live for any chunk the batch validator rejects, and it is the one patch-API window that the shared knob doesn't reach. The fix is small and removes a duplicate pacing constant.
Proposed change
proxy_batch_slotsand the chunking fromutils::concurrentinstead ofPROXY_BATCH_PATH_CONCURRENCY. Read the policy at client construction withapi_concurrency(use_public_proxy), so the env override and the fd-limit rule apply; for the proxy that is 4, or lower when the override asks.PROXY_BATCH_PATH_CONCURRENCY.registry_concurrency()andREGISTRY_CONCURRENCY, or wire them into the pristine-registry fetch they describe. Deleting is preferred unless a caller exists by then.Size and scope
api/client.rs(constant, constructor, fallback loop, test) andutils/concurrent.rs: about 20 production lines, plus an updated test. Out of scope: unifying the retry systems (C15) and batch-size limits (C16).Acceptance criteria
concurrent_batches_share_the_legacy_fallback_capasserts that the peak equalsapi_concurrency(true)(4), not 10.SOCKET_API_CONCURRENCY=1(serial), the per-package fallback's peak in flight is 1.utils::concurrent.cargo test -p socket-patch-core api::clientandproxy_batch_e2estay green.Dependencies
Touches
api/client.rs, which open PRs #607 and #610 also edit, in different functions. Blocks nothing.