✓ProveIt
Examples / Report #4023192 Saved analysis

CVE-2026-92390: -J no-clobber guarantee bypassed with 103 early hints

curl · 2026-09-1213.0s analysis · $0.012 in model calls

What the report claims

A server can bypass curl's -J no-clobber guarantee by sending a 103 Early Hints response before the final response, causing the output file to be truncated before curl checks for overwriting.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2026-92390 -J no-clobber guarantee bypassed with 103 early hints

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Needs human review

The available evidence does not settle the report. A maintainer should investigate.

Why this finding?
  • The provided curl source code (lib/urldata.h lines 78-162) does not contain the relevant logic for -J, -i, Content-Disposition, Early Hints (103), or file opening/clobber handling. All claims are not_verifiable against the given code.
  • The deterministic checks only confirm symbol existence (ETag, Location, control, honor_cd_filename) but do not show their usage.
  • The RFC 8297 excerpt confirms the external requirement that 103 Early Hints must not affect final-response processing, which aligns with the report's argument.
  • Without the actual header processing and file handling code (likely in lib/http.c, tool_operate.c, or similar), the claims cannot be confirmed or contradicted.
What still needs checking
  • Source code for HTTP response header processing (especially informational/1xx responses)
  • Source code for Content-Disposition parsing and -J filename handling
  • Source code for -i/--include header display and output file opening logic
  • Source code for --etag-save ETag handling
  • The honor_cd_filename flag usage and where the no-clobber (O_EXCL) open occurs
How this analysis ran 5 claims · 0 citations verified

Checked against curl master on 2026-09-12 (04bfe8fbbf55). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 5 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 2710 tokens · $0.0003
3 · Evidence
1 code snippets
real curl source at 04bfe8fbbf55 · 3 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
9223 tokens · 10.7s · $0.0121
5 · Receipts
0 of 0 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the detailed report. The provided code snippet (lib/urldata.h) does not contain the HTTP header processing, Content-Disposition handling, or file opening logic needed to verify the claims. Could you point to the specific files/functions where: (1) 103 Early Hints responses are handled, (2) -i header display opens the output file, (3) Content-Disposition filename is parsed and the -J no-clobber check occurs, and (4) honor_cd_filename is set/checked? With those locations I can assess whether the early open bypasses the O_EXCL protection.

Inspect all claims and source code 5 claims · 1 code excerpts

Report claims

C1flaw not verifiable

The no-clobber guarantee of -J is bypassed when a 103 Early Hints response opens the output file before Content-Disposition parsing

Read the reasoning

The provided source code (lib/urldata.h lines 78-162) only defines CURLcode typedefs and some structs. It does not contain the header processing logic, file opening logic, Content-Disposition handling, Early Hints (103) handling, or the -J/-i option implementations that would decide this claim. The code_refs 'CURLcode CURLMSG_DONE' and 'CURLcode CURLMSG_HEADER' appear to be message type constants, not the actual handling functions.

C2location not verifiable

The issue occurs in the header display path for informational responses and --etag-save handling

Read the reasoning

Same as C1: the provided source does not contain the header display path, informational response handling, or --etag-save handling code. The location of the flaw cannot be verified from the given code.

C3trigger not verifiable

Sending a 103 Early Hints response before the final response with a Content-Disposition filename matching the URL basename

Read the reasoning

The trigger mechanism (103 Early Hints causing early file open) depends on HTTP response handling code not present in the provided source snippet.

C4impact not verifiable

The pre-existing file is truncated and overwritten despite -J's documented protection

Read the reasoning

The impact claim (pre-existing file truncated despite -J protection) depends on the file opening logic and -J's O_EXCL check, which are not in the provided source.

C5reproduction not verifiable

Run curl -O -J -i against a server that sends 103 Early Hints first, causing the file to be truncated

Read the reasoning

The reproduction steps depend on the full curl command-line tool behavior, which cannot be verified from the single header file provided.

What the report says about other projects

confirmed Early Hints must not affect final-response processing

“Aside from performance optimizations, such evaluation of the 103 (Early Hints) response's header fields MUST NOT affect how the final response is processed.” source

Read the original report (excerpt)

Full report on HackerOne ↗

Hi all, I found a way for a server to defeat `--remote-header-name`'s (`-J`) no-clobber guarantee: with `curl -O -J -i`, sending a `103 Early Hints` before the final response (or an `ETag` ahead of `Content-Disposition` under `--etag-save`) lets a server overwrite an existing file it names via `Content-Disposition`, even though `-J` documents that exact case as protected. This is a post-CVE-2020-8177 variant of the same `-J` plus `-i` header-overwrite class. The earlier flaw was fixed in curl 7.71.0; this case reaches the same unsafe state through informational-response and `--etag-save` header ordering after support for combining `-J` with displayed headers was added. ## Why it happens `-J` promises not to overwrite an existing server-selected filename ([documented here](https://curl.se/docs/manpage.html#--remote-header-name)). Curl waits for a usable `Content-Disposition` header, or the 8.19.0 redirect `Location` fallback, before applying that check. With `-i`, curl normally buffers displayed headers while it waits for the filename. However, the displayed-header path still opens the output file for an HTTP 1xx response such as `103 Early Hints`. The same happens for an `ETag` line when `--etag-save` handles it before `Content-Disposition`. That early open uses the normal truncating mode on the URL-derived filename. When the final `Content-Disposition` arrives, curl sees that the output stream is already open and aborts. The protected no-clobber open is never reached, but the URL-derived file has already been truncated. If the URL is `.../victim.txt` and the server names `victim.txt`, this is the exact file that `-J` promises to protect. This also matches RFC 8297's requirement that Early Hints must not affect final-response processing. See [RFC 8297](https://datatracker.ietf.org/doc/rfc8297/). ## PoC Server sends `103 Early Hints`, then a final response whose `Content-Disposition` filename is identical to the URL basename and to a pre-existing local file. Control: same server, same file, no `103`. ```sh #!/bin/sh # Reproduces AISLE-2026-0115-00244: curl -O -J -i lets a server's 103 Early # Hints defeat -J's O_EXCL no-clobber protection when Content-Disposition # names the same file as the URL basename. set -e MODE="$1" # exploit (103 first) | control (no 103) PORT="${VF_PORT:-18080}" WORK="${VF_WORK:-/tmp/vf-j-noclob}" mkdir -p "$WORK"; cd "$WO …

Source code

lib/urldata.hlines 78–162 · definition of CURLcode
78typedef CURLcode (Curl_send)(struct Curl_easy *data, /* transfer */
79 int8_t sockindex, /* socketindex */
80 const uint8_t *buf, /* data to write */
81 size_t len, /* amount to send */
82 bool eos, /* last chunk */
83 size_t *pnwritten); /* how much sent */
84
85/* On error return, the value of `pnread` has no meaning */
86typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */
87 int8_t sockindex, /* socketindex */
88 char *buf, /* store data here */
89 size_t len, /* max amount to read */
90 size_t *pnread); /* how much received */
91
92#include "mime.h"
93#include "protocol.h"
94#include "ftp.h"
95#include "http.h"
96#include "smb.h"
97#include "mqtt.h"
98#include "ftplistparser.h"
99#include "multihandle.h"
100#include "cf-socket.h"
101
102#ifdef HAVE_GSSAPI
103# ifdef HAVE_GSSAPPLE
104# include <GSS/gssapi.h>
105# include <GSS/gssapi_oid.h>
106# elif defined(HAVE_GSSGNU)
107# include <gss.h>
108# elif defined(HAVE_GSSAPI_H)
109# include <gssapi.h>
110# else /* MIT Kerberos */
111# include <gssapi/gssapi.h>
112# include <gssapi/gssapi_krb5.h> /* for GSS_C_CHANNEL_BOUND_FLAG in 1.19+ */
113# endif
114#endif
115
116#ifdef USE_LIBSSH2
117#include <libssh2.h>
118#include <libssh2_sftp.h>
119#endif /* USE_LIBSSH2 */
120
121#define READBUFFER_SIZE CURL_MAX_WRITE_SIZE
122#define READBUFFER_MAX CURL_MAX_READ_SIZE
123#define READBUFFER_MIN 1024
124
125/* The default upload buffer size, should not be smaller than
126 CURL_MAX_WRITE_SIZE, as it needs to hold a full buffer as could be sent in
127 a write callback.
128
129 The size was 16KB for many years but was bumped to 64KB because it makes
130 libcurl able to do significantly faster uploads in some circumstances. Even
131 larger buffers can help further, but this is deemed a fair memory/speed
132 compromise. */
133#define UPLOADBUFFER_DEFAULT 65536
134#define UPLOADBUFFER_MAX (2 * 1024 * 1024)
135#define UPLOADBUFFER_MIN CURL_MAX_WRITE_SIZE
136
137#ifdef USE_WINDOWS_SSPI
138#include "curl_sspi.h"
139#endif
140
141#ifndef CURL_DISABLE_DIGEST_AUTH
142/* Struct used for Digest challenge-response authentication */
143struct digestdata {
144 struct Curl_creds *creds;
145 struct Curl_peer *origin;
146#ifdef USE_WINDOWS_SSPI
147 BYTE *input_token;
148 size_t input_token_len;
149 CtxtHandle *http_context;
150#else
151 char *nonce;
152 char *cnonce;
153 char *realm;
154 char *opaque;
155 char *qop;
156 char *algorithm;
157 int nc; /* nonce count */
158 uint8_t algo;
159 BIT(stale); /* set true for re-negotiation */
160 BIT(userhash);
161#endif
162};