Skip to content

fix: validate s3 sync settings and explain s3 errors - #57

Open
sudores wants to merge 1 commit into
brokenpip3:mainfrom
sudores:fix/lack-of-validation-s3-sync
Open

fix: validate s3 sync settings and explain s3 errors#57
sudores wants to merge 1 commit into
brokenpip3:mainfrom
sudores:fix/lack-of-validation-s3-sync

Conversation

@sudores

@sudores sudores commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Refs #51

Problem

S3 answers a mistyped key, a newline picked up while pasting and a misspelled region with the same opaque SignatureDoesNotMatch, so there was no way for a user to tell a wrong credential from a wrong region — which is exactly where #51 stalled.

Changes

  • Validate the settings that can be checked without a network round trip, in SettingsViewModel and again in sync_aws, so credentials that never went through the settings screen are covered too. Checks that only hold for real AWS (key shapes, region names) are skipped when a custom endpoint is set, since minio, Garage, R2 and the rest pick their own.
  • Explain the S3 error codes users actually hit (SignatureDoesNotMatch, NoSuchBucket, AccessDenied, PermanentRedirect/AuthorizationHeaderMalformed, RequestTimeTooSkewed), keeping the original message for reference.
  • Stop the keyboard from rewriting credentials — auto-correction, word suggestions and automatic capitalization all alter what was typed, and the result surfaces only as an authentication failure. Credential fields are now singleLine with capitalization and autocorrect off.
  • Document the S3 fields per provider in the README.

Verification

The existing integration test only covered path-style with an endpoint URL. Real AWS takes a different path — no endpoint means force_path_style: false, virtual-hosted addressing, and the region participating in the SigV4 signature — which was untested.

I exercised that path by pointing AWS_ENDPOINT_URL_S3 (honored by taskchampion via aws_config::defaults()) at a minio configured with MINIO_DOMAIN + wildcard DNS and region=eu-west-2, using AWS-shaped credentials. The client code path is then identical to real AWS, against a server that enforces the region.

Scenario Result
AWS mode (virtual-hosted + region-signed), push→pull roundtrip works
Path-style S3-compatible (endpoint set), roundtrip works
Path-style with a non-AWS region name (garage) works
Pasted values padded with spaces/tabs/newlines trimmed, syncs
Wrong region in AWS mode AuthorizationHeaderMalformed → "The bucket lives in a different region than the one configured…"
Wrong secret in AWS mode SignatureDoesNotMatch → "S3 rejected the request signature. The access key ID and secret access key must be copied exactly…"

No false rejections across 12 real provider config shapes — AWS (eu-west-2, no region, GovCloud, China, dotted bucket), minio, Cloudflare R2 (region auto, 32/64-hex creds), Backblaze B2, Wasabi, DigitalOcean Spaces, Garage, and a generic short-credential service. 7 malformed configs are each rejected with a message naming the field. The Rust and Kotlin validators produce identical verdicts and messages across the whole matrix.

cargo test --lib (29 passed), cargo clippy --all-targets -D warnings and cargo fmt --check are clean; the S3 integration test passes against the compose minio.

Known gaps, not addressed here

  • Connection failures still surface as a bare unhandled error: taskchampion::Error::Other is #[error(transparent)] over anyhow, and only aws-sdk's Unhandled variant appends a code, so the dispatch failure / error trying to connect branches never match. Wants a catch-all or walking source().
  • An endpoint URL pointing at AWS itself (https://s3.eu-west-2.amazonaws.com) bypasses the AWS key/region checks and switches to path-style addressing.
  • Bucket-name rules are applied even with a custom endpoint, so an underscore bucket (allowed by Ceph RGW) or a legacy uppercase us-east-1 bucket would be refused.
  • The taskrc sync.aws.* keys from S3 sync, SignatureDoesNotMatch #51 are untouched — that is taskrc import should import the storage settings #52.

Caveat

This was not tested against real AWS S3 (no account available). What is verified is the identical client code path against a region-enforcing S3 server, which covers the addressing and signing behaviour but not AWS-specific server quirks.

🤖 Generated with Claude Code

S3 answers a mistyped key, a newline picked up while pasting and a
misspelled region with the same opaque SignatureDoesNotMatch, so the
settings form gave no way to tell a wrong credential from a wrong
region.

Validate the fields that can be checked without talking to the server,
in the settings view model and again in sync_aws so credentials that
never went through the screen are covered too. Checks that only hold
for real AWS (key shapes, region names) are skipped when a custom
endpoint is set, since minio, Garage and the other S3-compatible
services pick their own.

Turn the S3 error codes users actually hit into an explanation of what
to check, keeping the original message for reference.

Stop the keyboard from rewriting credentials: auto-correction, word
suggestions and automatic capitalization all alter what was typed, and
the result is only reported as an authentication failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@brokenpip3 brokenpip3 self-assigned this Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants