✓ProveIt
Examples / Report #3418646 Saved analysis

Arbitrary Configuration File Inclusion: via External Control of File Name or Path

curl · 2025-11-1011.3s analysis · $0.013 in model calls

What the report claims

The report claims an Arbitrary Configuration File Inclusion vulnerability in curl's --config option allows attackers to read arbitrary files and write to arbitrary locations via malicious configuration files.

Compare with curl's recorded outcome

Labeled AI slop

curl's maintainer lists this report as AI slop

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

ProveIt's finding

Needs a policy review

The code does what the report says, but curl's own policy may exclude it. A maintainer decides.

curl policy: Tricking a user to run a command line
Judgment withdrawn. The answer failed the evidence check.
See the withdrawn model reasoning

The following reasoning failed the citation check and does not support the final finding.

  • The --config option is a documented feature where the user explicitly specifies which configuration file to load. Reading that file and processing its contents (including file:// URLs and output directives) is intended behavior.
  • The attack requires tricking a user into running a crafted command line (curl --config /path/to/malicious.config). The project policy explicitly classifies this as 'not a security problem': 'if an attacker can trick the user to run a specifically crafted curl command line, all bets are off.'
  • No code showing a path validation bypass or unintended file access is provided. The report describes the feature working as designed.
How this analysis ran 5 claims · 1 citations verified

Evidence check: the behavior is real; the project's policy section 'Tricking a user to run a command line' may exclude it

Checked against curl master on 2025-11-10 (a8e46c5ab1d9). 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 · 4 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 2114 tokens · $0.0002
3 · Evidence
2 code snippets
real curl source at a8e46c5ab1d9
4 · Judgment
Nemotron-3-Ultra-550b-a55b
8447 tokens · 8.4s · $0.0126
5 · Receipts
1 of 5 citations verified
every citation must point at code the judge was shown
Inspect the withdrawn draft reply

This draft belongs to the withdrawn judgment. It may contain unsupported claims.

This report describes curl's documented --config feature working as intended. The user explicitly provides the configuration file path via --config, and curl processes its contents (including file:// URLs and output directives) by design. The project policy explicitly states that tricking a user into running a crafted command line is not a security problem. No validation bypass or unintended file access is demonstrated.

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

This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.

Report claims

C1flaw contradicted

curl reads configuration files without adequate path validation, allowing arbitrary file inclusion

Read the reasoning

The report frames the --config option as a vulnerability because it allows reading a user-specified file. However, --config is a documented feature where the user explicitly provides the configuration file path. The 'arbitrary file inclusion' is the intended behavior — the user chooses which config file to load. No code path showing a validation bypass is provided; the provided snippets only show getparameter and curlx_fopen definitions, not the --config handling code.

project_policy:0
C2location not verifiable

in the --config option processing

Read the reasoning

The actual --config processing code (where the file is opened and each line parsed) is not included in the provided source. The claim cannot be verified against the given code.

C3trigger supported

using a user-supplied path in the --config argument

Read the reasoning

The trigger is correctly described: the user supplies a path via --config. This is the documented interface for the feature, not a vulnerability.

report:0
C4impact supported

reads sensitive files like /etc/passwd and writes arbitrary files

Read the reasoning

The described impact (reading /etc/passwd via file:// URL and writing output) is achievable through documented curl config file syntax. This demonstrates the feature working as designed, not a flaw.

report:0
C5reproduction supported

create /tmp/malicious.curlrc with url = "file:///etc/passwd" and output = "/tmp/stolen_passwd.txt", then run curl --config /tmp/malicious.curlrc

Read the reasoning

The reproduction steps correctly demonstrate curl's documented --config functionality. They do not demonstrate a vulnerability.

report:0
Read the original report (excerpt)

Full report on HackerOne ↗

## Summary: The Arbitrary Configuration File Inclusion (ACFI) vulnerability was identified in the curl utility via the --config <file> option. This flaw is a form of External Control of File Name or Path (CWE-73), occurring due to the lack of adequate validation on the user-supplied configuration file path. An attacker can leverage this weakness to: Trick a user into executing curl with a malicious configuration file located at an arbitrary path (e.g., /tmp/malicious.curlrc). Significantly control curl's behavior, including setting dangerous options such as url = "file:///" and output = "...". The impact is Critical, potentially allowing the attacker to perform a Local File Read of sensitive files like /etc/passwd and an Arbitrary File Write to arbitrary locations on the victim's system. "I confirm that I performed the vulnerability discovery and core technical analysis manually. However, AI tools (such as Gemini/ChatGPT) were utilized solely for summarizing the findings, calculating the CVSS score, and drafting the formal report structure based on my raw technical data. AI was not used to generate the exploit code or perform the scan/discovery." ## Affected version curl/libcurl version : 8.15.0 platform : x86_64-pc-linux-gnu ## Steps To Reproduce: [add details for how we can reproduce the issue] 1. create a malicious configuration file : Open the terminal and run the following command to create a file named /tmp/malicious.curlrc. This file will instruct curl to read the /etc/passwd file and save it to /tmp/stolen_passwd.txt. echo 'url = "file:///etc/passwd"' > /tmp/malicious.curlrc echo 'output = "/tmp/stolen_passwd.txt"' >> /tmp/malicious.curlrc 2. and then Run curl and direct it to use the configuration file you just created using the --config curl --config /tmp/malicious.curlrc 3. Then we check whether the file /tmp/stolen_passwd.txt has been successfully created and contains the contents of /etc/passwd. cat /tmp/stolen_passwd.txt The results are in. curl executes instructions from configuration files without warning, reads sensitive local files (/etc/passwd), and writes them to a location specified by the attacker (/tmp/stolen_passwd.txt). This proves that attackers can read arbitrary local files and write to locations accessible to users running curl ## Supporting Material/References: This vulnerability stems from the way curl p …

Source code

src/tool_getparam.clines 2835–2995 · definition of getparameter (truncated)
2835ParameterError getparameter(const char *flag, /* f or -long-flag */
C2: getparameter function is shown but the --config option parsing and config file opening code is not in the provided snippets
2836 const char *nextarg, /* NULL if unset */
2837 bool *usedarg, /* set to TRUE if the arg
2838 has been used */
2839 struct OperationConfig *config,
2840 int max_recursive)
2841{
2842 const char *parse = NULL;
2843 bool longopt = FALSE;
2844 bool singleopt = FALSE; /* when true means '-o foo' used '-ofoo' */
2845 ParameterError err = PARAM_OK;
2846 bool toggle = TRUE; /* how to switch boolean options, on or off. Controlled
2847 by using --OPTION or --no-OPTION */
2848 bool nextalloc = FALSE; /* if nextarg is allocated */
2849 bool consumearg = TRUE; /* the argument comes separate */
2850 const struct LongShort *a = NULL;
2851 verbose_nopts = 0; /* options processed in `flag`*/
2852
2853 *usedarg = FALSE; /* default is that we do not use the arg */
2854
2855 if(('-' != flag[0]) || ('-' == flag[1])) {
2856 /* this should be a long name */
2857 const char *word = ('-' == flag[0]) ? flag + 2 : flag;
2858 bool noflagged = FALSE;
2859 bool expand = FALSE;
2860 const char *p;
2861 struct Curl_str out;
2862
2863 if(!strncmp(word, "no-", 3)) {
2864 /* disable this option but ignore the "no-" part when looking for it */
2865 word += 3;
2866 toggle = FALSE;
2867 noflagged = TRUE;
2868 }
2869 else if(!strncmp(word, "expand-", 7)) {
2870 /* variable expansions is to be done on the argument */
2871 word += 7;
2872 expand = TRUE;
2873 }
2874
2875 p = word;
2876 /* is there an '=' ? */
2877 if(!curlx_str_until(&p, &out, MAX_OPTION_LEN, '=') &&
2878 !curlx_str_single(&p, '=') ) {
2879 /* there's an equal sign */
2880 char tempword[MAX_OPTION_LEN + 1];
2881 memcpy(tempword, curlx_str(&out), curlx_strlen(&out));
2882 tempword[curlx_strlen(&out)] = 0;
2883 a = findlongopt(tempword);
2884 nextarg = p;
2885 consumearg = FALSE; /* it is not separate */
2886 }
2887 else
2888 a = findlongopt(word);
2889
2890 if(a) {
2891 longopt = TRUE;
2892 }
2893 else {
2894 err = PARAM_OPTION_UNKNOWN;
2895 goto error;
2896 }
2897 if(noflagged && (ARGTYPE(a->desc) != ARG_BOOL)) {
2898 /* --no- prefixed an option that is not boolean! */
2899 err = PARAM_NO_PREFIX;
2900 goto error;
2901 }
2902 else if(expand && nextarg) {
2903 struct dynbuf nbuf;
2904 bool replaced;
2905
2906 if((ARGTYPE(a->desc) != ARG_STRG) &&
2907 (ARGTYPE(a->desc) != ARG_FILE)) {
2908 /* --expand on an option that is not a string or a filename */
2909 err = PARAM_EXPAND_ERROR;
2910 goto error;
2911 }
2912 err = varexpand(nextarg, &nbuf, &replaced);
2913 if(err) {
2914 curlx_dyn_free(&nbuf);
2915 goto error;
2916 }
2917 if(replaced) {
2918 nextarg = curlx_dyn_ptr(&nbuf);
2919 nextalloc = TRUE;
2920 }
2921 }
2922 }
2923 else {
2924 flag++; /* prefixed with one dash, pass it */
2925 parse = flag;
2926 }
2927
2928 do {
2929 /* we can loop here if we have multiple single-letters */
2930 if(!longopt) {
2931 a = findshortopt(*parse);
2932 if(!a) {
2933 err = PARAM_OPTION_UNKNOWN;
2934 break;
2935 }
2936 toggle = !(a->desc & ARG_NO);
2937 }
2938 if((a->desc & ARG_TLS) && !feature_ssl) {
2939 err = PARAM_LIBCURL_DOESNT_SUPPORT;
2940 break;
2941 }
2942 else if(ARGTYPE(a->desc) >= ARG_STRG) {
2943 /* this option requires an extra parameter */
2944 if(!longopt && parse[1]) {
2945 nextarg = &parse[1]; /* this is the actual extra parameter */
2946 singleopt = TRUE; /* do not loop anymore after this */
2947 }
2948 else if(a->cmd == C_HELP) {
2949 /* --help is special */
2950 tool_help((nextarg && *nextarg) ? nextarg : NULL);
2951 err = PARAM_HELP_REQUESTED;
2952 break;
2953 }
2954 else if(!nextarg) {
2955 err = PARAM_REQUIRES_PARAMETER;
2956 break;
2957 }
2958 else {
2959 *usedarg = consumearg; /* mark it as used */
2960 }
2961 if(a->desc & ARG_DEPR) {
2962 opt_depr(a);
2963 break;
2964 }
2965
2966 if(has_leading_unicode((const unsigned char *)nextarg)) {
2967 warnf("The argument '%s' starts with a Unicode character. "
2968 "Maybe ASCII was intended?", nextarg);
2969 }
2970 if(ARGTYPE(a->desc) == ARG_FILE)
2971 err = opt_file(config, a, nextarg, max_recursive);
2972 else /* if(ARGTYPE(a->desc) == ARG_STRG) */
2973 err = opt_string(config, a, nextarg);
2974 if(a->desc & ARG_CLEAR)
2975 cleanarg(CURL_UNCONST(nextarg));
2976 }
2977 else {
2978 if(a->desc & ARG_DEPR) {
2979 opt_depr(a);
2980 break;
2981 }
2982 /* ARG_NONE | ARG_BOOL */
2983 if(ARGTYPE(a->desc) == ARG_BOOL)
2984 err = opt_bool(config, a, toggle);
2985 else
2986 err = opt_none(config, a);
2987 }
2988
2989 ++verbose_nopts; /* processed one option from `flag` input, loop for
2990 more */
2991 } while(!longopt && !singleopt && *++parse && !*usedarg && !err);
2992
2993error:
2994 if(nextalloc)
2995 free(CURL_UNCONST(nextarg));
lib/curlx/fopen.hlines 51–51 · definition of curlx_fopen
51#define curlx_fopen(file,mode) curl_dbg_fopen(file,mode,__LINE__,__FILE__)