T09 · Insecure Skill Coding Practices
- Location
scripts/r2_uploader.py:27- Finding
Uploads Default to Public and the Private-by-Default Environment Setting Is Ignored
- Content
View full analysis
Vulnerability Details
File Location:
scripts/r2_uploader.py:27-58,scripts/r2_uploader.py:145-149, andscripts/r2_uploader.py:248-252
Vulnerability Type: Insecure access-control default
Risk Level: MediumThe constructor assigns
Truetodefault_publicby default:python default_public: bool = True,The environment variable is consulted only when
default_publicisNone. Because ordinary construction uses the non-null default valueTrue,CLOUDFLARE_R2_DEFAULT_PUBLIC=falseis ignored:python self.public_domain = public_domain or os.getenv("CLOUDFLARE_R2_PUBLIC_DOMAIN") self.default_public = default_public if default_public is not None else \ os.getenv("CLOUDFLARE_R2_DEFAULT_PUBLIC", "true").lower() == "true"Public uploads request a public-read ACL:
python extra_args = {} if public: extra_args['ACL'] = 'public-read'The command-line interface also makes uploads public unless the user explicitly supplies
--private:python result = uploader.upload_file( args.file_path, args.object_name, public=not args.private )The example configuration reinforces this unsafe default at
references/config_example.env:19:env CLOUDFLARE_R2_DEFAULT_PUBLIC=trueTechnical Analysis
Access to uploaded data should fail closed. This implementation instead treats public access as the normal state and requires callers to opt out.
More importantly, the constructor's default argument prevents the documented environment variable from controlling behavior during normal initialization. Calling
R2Uploader()setsdefault_publictoTrue, so the conditional expression never evaluatesCLOUDFLARE_R2_DEFAULT_PUBLIC. A user can therefore configure the variable asfalseand still receive public behavior.In S3-compatible services that honor object ACLs, the code requests
public-read. For an R2 bucket whose public devel ...[truncated 1649 chars]- Remediation
View remediation
Remediation Suggestions
-
Change the default to private:
python default_public: Optional[bool] = None -
Parse the environment setting only when no explicit argument is supplied, and use a secure fallback:
python if default_public is None: self.default_public = ( os.getenv("CLOUDFLARE_R2_DEFAULT_PUBLIC", "false").strip().lower() == "true" ) else: self.default_public = default_public -
Replace the CLI's opt-out
--privateflag with an explicit opt-in--publicflag:python upload_parser.add_argument( "--public", action="store_true", help="Explicitly request public access" ) -
Pass
public=args.publicduring upload so omitted flags remain private. -
Do not rely on object ACLs to provide privacy for objects stored in a publicly exposed R2 bucket. Use a separate private bucket for sensitive content, disable the bucket's public development/custom domain, and use short-lived presigned URLs when sharing is required.
-
Return a public URL only when public access was explicitly requested and successfully verified. For private objects, return an object identifier or presigned URL instead.
-
Update
references/config_example.envsoCLOUDFLARE_R2_DEFAULT_PUBLIC=false. -
Add tests confirming that
R2Uploader()honors an environment value offalseand that CLI uploads are private unless public access is explicitly requested.
-
