Arbitrary Configuration File Inclusion: via External Control of File Name or Path
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
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 lineSee 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.
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
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.
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.
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.
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.
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.
Read the original report (excerpt)
Source code
| 2835 | ParameterError 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 | |
| 2993 | error: |
| 2994 | if(nextalloc) |
| 2995 | free(CURL_UNCONST(nextarg)); |
| 51 | #define curlx_fopen(file,mode) curl_dbg_fopen(file,mode,__LINE__,__FILE__) |