s7cmd 1.7.0

Reliable, flexible, and fast command-line tool for Amazon S3
s7cmd-1.7.0 is not a library.

s7cmd

Crates.io GitHub License CI codecov

A reliable, flexible, and fast command-line tool for Amazon S3.

s7cmd combines the speed of a Rust async runtime with the breadth of the AWS S3 API surface, providing high-throughput object operations (ls, cp, mv, rename, rm, sync, clean) alongside comprehensive bucket administration (lifecycle, policy, encryption, object annotations, CORS, public access block, website, logging, notification, and more) — all from a single static binary.

s7cmd is a thin command-line wrapper over four Rust libraries by the same author: s3sync, s3ls-rs, s3util-rs, and s3rm-rs. Originally, only s3sync and s3rm-rs existed and were not intended to be merged, but in response to user requests for a unified interface, the functionality was split into focused libraries and bundled together into a single binary as s7cmd. Built on aws-sdk-rust and tokio, s7cmd targets workloads that demand both performance and operational completeness: data engineering pipelines, ML training data preparation, multi-account bucket governance, and integrity-critical migrations.

Features highlights

s7cmd is designed to pursue reliability and performance as equal goals, rather than trading one for the other.

  • Single binary, full coverage. Object transfer, bulk delete, and every common bucket-level configuration in one tool.
  • Strong integrity verification. Native support for SHA256, SHA1, CRC32, CRC32C, and CRC64NVME — aligned with S3's 2026 default checksum policy.
  • Predictable performance. Configurable workers, multipart thresholds, and chunk sizes; bounded memory footprint suitable for small instances and large CI runners alike.
  • Apache-2.0 licensed. No copyleft concerns for enterprise deployment or container distribution.

About the name

The name follows the s3cmd / s4cmd / s5cmd / s6cmd lineage, but s7cmd is not affiliated with, derived from, or compatible with any of them. The number 7 was chosen simply because it was the next available one. There is no deeper meaning.

Usage

Usage: s7cmd [OPTIONS] [COMMAND]

Object Operations:
  ls                                    List S3 objects
  cp                                    Copy objects from/to S3 (or S3 to S3)
  mv                                    Move objects from/to S3 (copy then delete source)
  rename                                Rename an S3 object within an Express One Zone bucket
  rm                                    Delete a single S3 object
  restore-object                        Restore an archived S3 object
  presign                               Generate a pre-signed URL for an S3 object (GET only)
  sync                                  Synchronize files between local and S3 (or S3 to S3)
  clean                                 Bulk-delete S3 objects

Object Metadata:
  head-object                           Head an S3 object
  get-object-tagging                    Get an S3 object's tagging
  put-object-tagging                    Put tagging on an S3 object
  delete-object-tagging                 Delete tagging from an S3 object

Object Annotation:
  get-object-annotation                 Download a named annotation payload from an S3 object
  put-object-annotation                 Attach a named annotation payload to an S3 object
  delete-object-annotation              Delete a named annotation from an S3 object
  list-object-annotations               List the annotations of an S3 object and print them as JSON

Bucket Operations:
  create-bucket                         Create an S3 bucket
  delete-bucket                         Delete an S3 bucket
  head-bucket                           Head an S3 bucket

Bucket Tagging:
  get-bucket-tagging                    Get a bucket's tagging
  put-bucket-tagging                    Put tagging on a bucket
  delete-bucket-tagging                 Delete tagging from a bucket

Bucket Policy:
  get-bucket-policy                     Get a bucket's policy
  put-bucket-policy                     Put a bucket policy
  delete-bucket-policy                  Delete a bucket's policy

Bucket Versioning:
  get-bucket-versioning                 Get a bucket's versioning configuration
  put-bucket-versioning                 Put a bucket versioning configuration

Bucket Lifecycle:
  get-bucket-lifecycle-configuration    Get a bucket's lifecycle configuration
  put-bucket-lifecycle-configuration    Put a bucket lifecycle configuration
  delete-bucket-lifecycle-configuration Delete a bucket's lifecycle configuration

Bucket Encryption:
  get-bucket-encryption                 Get a bucket's encryption configuration
  put-bucket-encryption                 Put a bucket encryption configuration
  delete-bucket-encryption              Delete a bucket's encryption configuration

Bucket CORS:
  get-bucket-cors                       Get a bucket's CORS configuration
  put-bucket-cors                       Put a bucket CORS configuration
  delete-bucket-cors                    Delete a bucket's CORS configuration

Bucket Public Access Block:
  get-public-access-block               Get a bucket's public access block configuration
  put-public-access-block               Put a bucket public access block configuration
  delete-public-access-block            Delete a bucket's public access block configuration

Bucket Website:
  get-bucket-website                    Get a bucket's website configuration
  put-bucket-website                    Put a bucket website configuration
  delete-bucket-website                 Delete a bucket's website configuration

Bucket Logging:
  get-bucket-logging                    Get a bucket's logging configuration
  put-bucket-logging                    Put a bucket logging configuration

Bucket Notification:
  get-bucket-notification-configuration Get a bucket's notification configuration
  put-bucket-notification-configuration Put a bucket notification configuration

Bucket Replication:
  get-bucket-replication                Get a bucket's replication configuration
  put-bucket-replication                Put a bucket replication configuration
  delete-bucket-replication             Delete a bucket's replication configuration

Bucket Transfer Acceleration:
  get-bucket-accelerate-configuration   Get a bucket's transfer acceleration configuration
  put-bucket-accelerate-configuration   Put a bucket transfer acceleration configuration

Bucket Request Payment:
  get-bucket-request-payment            Get a bucket's request payment configuration
  put-bucket-request-payment            Put a bucket request payment configuration

Bucket Policy Status:
  get-bucket-policy-status              Get a bucket's policy status (whether it is public)

Batch:
  batch-run                             Run s7cmd commands from a file (or - for stdin)

Other:
  help                                  Print this message or the help of the given subcommand(s)

Options:
      --auto-complete-shell <SHELL>  Generate shell completions for s7cmd (all subcommands) and exit [possible values: bash, elvish, fish, powershell, zsh]
  -h, --help                         Print help (see more with '--help')
  -V, --version                      Print version

batch-run

Reads s7cmd commands from a file (or stdin via -), one per line, and executes them in the same process — avoiding the per-command fork/exec, dynamic linker work, and Rust/tokio runtime startup you would pay if you invoked s7cmd once per line from a shell loop. (AWS SDK clients and TLS connections are still built per dispatched command, so network-side overhead is not eliminated; the win is process startup.) It is the recommended way to drive thousands of small operations (per-object tagging, mixed bucket-config edits, etc.) without spawning a process per command.

Note that although batch-run avoids launching a separate process for each command, it still initializes a new AWS client per command. This incurs per-command overhead such as credential resolution, region resolution, and HTTP client setup, so batch-run is not intended for high-throughput parallel processing of large workloads. In my EC2 environment, even after disabling IMDS, copying small files was limited to about 1,000 copies per second (with --parallel 32). Using the sync subcommand allows you to copy more than 3,000 files per second under similar conditions (though you'll need to adjust the --worker-size parameter), so there is a trade-off between flexibility and speed.

Usage: s7cmd batch-run [OPTIONS] <FILE>

<FILE> is a path to a script file, or - to read from stdin (mirrors put-bucket-policy).

Input format. One command per line. Each line is tokenized with shell-style quoting (POSIX shlex), so quoted arguments with spaces work as expected. Blank lines are ignored. Lines whose first non-whitespace character is # are treated as comments and skipped. Each line is parsed as if it were a top-level s7cmd invocation without the leading s7cmd — i.e. start with the subcommand name:

# create two buckets, then tag one of them
create-bucket s3://example-bucket-1
create-bucket s3://example-bucket-2
put-bucket-tagging --tagging "team=data&env=prod" s3://example-bucket-1

# upload a file with a key that contains spaces
cp ./report.csv "s3://example-bucket-1/reports/Q1 2026.csv"

Pass the script as a file argument, or pipe it in via -:

s7cmd batch-run commands.txt
s7cmd batch-run - < commands.txt

Execution modes. Two read modes and two parallelism modes, freely combined:

Flag Effect
(default) Read the whole script first, validate every line, then execute. Catches bad lines before any line runs. Shows a progress bar when stderr is a TTY.
--streaming Execute commands as they are read. No progress bar. Use for unbounded or pipelined input where buffering the whole script is undesirable.
--parallel 1 (default) Sequential execution. Lines run in script order.
--parallel N Run up to N commands concurrently (max 1024; a larger value is rejected at parse time). Completion order is not guaranteed.
--parallel 0 Use all logical CPUs. Completion order is not guaranteed.

Script order is preserved only with --parallel 1. With --parallel N (or --parallel 0), commands may complete in any order; do not rely on later lines observing the effects of earlier ones.

Default mode loads the entire script into memory, so very large scripts will use proportional memory. Use --streaming to execute lines as they are read.

Failure handling. By default, the first failing command stops sequential execution and prevents new spawns in parallel mode. Pass --continue-on-error to run every line regardless, or --max-errors N (N ≥ 1) to keep running up to N failures and then stop gracefully (sequential: stops after the N-th failure; parallel: stops spawning new commands once N failures have been recorded — in-flight commands complete). Pass --continue-on-warning to keep running past per-line warnings (exit codes 3 and 4 — EXIT_CODE_WARNING and EXIT_CODE_NOT_FOUND) while still stopping on true failures according to --max-errors (or the default first-failure stop). --continue-on-error is mutually exclusive with both --max-errors and --continue-on-warning. The process exit code is the worst seen across all executed commands, ranked by severity rather than numeric value: 1 (error) > 2 (invalid args) > 3 (warning) > 4 (not found) > any other non-zero (e.g. 130 SIGINT) > 0. So a run mixing exit 1 and exit 130 exits 1, not 130.

Lines that can't be parsed or validated (quoting errors, unknown subcommands, missing or invalid arguments, empty commands) count as failures the same way runtime failures do — they synthesize exit code 2, log at error level, increment the failed bucket, and count toward --max-errors / --continue-on-error. So --max-errors 5 will let you tolerate up to 5 typo'd lines anywhere in the script. (True read I/O errors — line over the 16 KiB cap, non-UTF-8 bytes, file unreadable — still abort the whole run.)

Format check. Pass --check-format to validate the script without executing anything. The walk stops at the first parse or validation problem (or read I/O error), reports that line as a single error-level log entry — identifying the script source (file path, or stdin for -) and the line number — and exits 1. On a clean pass a "format OK" message is emitted.

Per-line tracing. Each dispatched line emits a start event and a matching outcome event (success, warning (exit N), skipped (exit 130), or failure (exit N)) prefixed with the line number and the original input text. start and success are info level (silent at the default warn level — pass -v to see them); warning and skipped are warn level and failure is error level, all three visible without -v. If a line carries an inline credential option (--*-access-key, --*-secret-access-key, --*-session-token, --*-sse-c-key, --*-sse-c-key-md5), its value is masked to **** in the logged text.

Tracing flags belong to batch-run, not per-line. Pass --json-tracing, --aws-sdk-tracing, --span-events-tracing, --disable-color-tracing, and -v/-q to batch-run itself — e.g. s7cmd batch-run --aws-sdk-tracing commands.txt. Lines that set --json-tracing, --aws-sdk-tracing, --span-events-tracing, or --disable-color-tracing are rejected at validation time; per-line -v/-q is silently ignored (the tracing subscriber is installed once, at the top of the run).

Caveats and safety.

  • If you're not concerned with performance, it's best to leave --parallel at its default setting and run the process in series. There are many factors to consider when parallelizing.
  • Even when you increase the parallelism level (--parallel), the various rate limits apply on a per-command basis (they are not divided across or aggregated over the workers).
  • Increasing --parallel may increase the load on the operating system. It consumes CPU, memory, file descriptors, and other resources — pick a value the host and the target service can absorb.
  • On EC2 instances using an IAM instance profile, setting --parallel too high is likely to trigger IMDS-related errors (credential resolution hits the instance metadata service per command, and IMDS will throttle under heavy concurrent load).
  • The failure threshold in parallel mode is "stop spawning new commands", not "cancel in-flight commands." When --parallel N is close to or exceeds the number of script lines, every line may already be in flight by the time the threshold trips, so the run completes as if no threshold were set. The threshold is most effective when the line count is significantly larger than N. To cancel work that is already in flight, send SIGINT (Ctrl-C); per- subcommand cancellation handlers propagate it into in-flight transfers.
  • batch-run is a dangerous command and must be used with caution. Whenever possible, perform a dry run by using each subcommand's --dry-run flag, and pass -v to batch-run itself to surface the per-line info-level logs for preliminary verification.

For example, suppose you want to create two buckets and tag one of them. First, prepare a dry-run script (sample_dry_run.txt) with each subcommand's --dry-run flag baked in:

# sample_dry_run.txt — preview only; nothing is sent to S3 except per-subcommand --dry-run client-side validation.
create-bucket --dry-run s3://example-bucket-1
create-bucket --dry-run s3://example-bucket-2
put-bucket-tagging --dry-run --tagging "team=data&env=prod" s3://example-bucket-1

Run it with -v on batch-run itself so the per-line start / success events and the per-subcommand [dry-run] would … info lines are visible. --no-progress is added so the live progress bar (drawn by default on TTY stderr) does not interleave with the log lines you want to read:

$ s7cmd batch-run -v --no-progress sample_dry_run.txt
2026-04-30T23:34:11.178191Z  INFO line started line=2 event="start" command="create-bucket" raw="create-bucket --dry-run s3://example-bucket-1"
2026-04-30T23:34:11.282653Z  INFO [dry-run] would create bucket. bucket=example-bucket-1
2026-04-30T23:34:11.282756Z  INFO line completed line=2 event="success" exit_code=0 command="create-bucket" raw="create-bucket --dry-run s3://example-bucket-1"
2026-04-30T23:34:11.282762Z  INFO line started line=3 event="start" command="create-bucket" raw="create-bucket --dry-run s3://example-bucket-2"
2026-04-30T23:34:11.283018Z  INFO [dry-run] would create bucket. bucket=example-bucket-2
2026-04-30T23:34:11.283038Z  INFO line completed line=3 event="success" exit_code=0 command="create-bucket" raw="create-bucket --dry-run s3://example-bucket-2"
2026-04-30T23:34:11.283040Z  INFO line started line=4 event="start" command="put-bucket-tagging" raw="put-bucket-tagging --dry-run --tagging \"team=data&env=prod\" s3://example-bucket-1"
2026-04-30T23:34:11.283239Z  INFO [dry-run] would put bucket tagging. bucket=example-bucket-1
2026-04-30T23:34:11.283284Z  INFO line completed line=4 event="success" exit_code=0 command="put-bucket-tagging" raw="put-bucket-tagging --dry-run --tagging \"team=data&env=prod\" s3://example-bucket-1"
batch-run: 3 succeeded, 0 failed, 0 warnings, 0 skipped, elapsed 0.1s

Once the dry run looks correct, run the same commands without --dry-run (sample.txt):

# sample.txt — the real run; this DOES create buckets and apply tags.
create-bucket s3://example-bucket-1
create-bucket s3://example-bucket-2
put-bucket-tagging --tagging "team=data&env=prod" s3://example-bucket-1
$ s7cmd batch-run -v --no-progress sample.txt
2026-04-30T23:35:42.418901Z  INFO line started line=2 event="start" command="create-bucket" raw="create-bucket s3://example-bucket-1"
2026-04-30T23:35:43.512214Z  INFO Bucket created. bucket=example-bucket-1
2026-04-30T23:35:43.512410Z  INFO line completed line=2 event="success" exit_code=0 command="create-bucket" raw="create-bucket s3://example-bucket-1"
2026-04-30T23:35:43.512430Z  INFO line started line=3 event="start" command="create-bucket" raw="create-bucket s3://example-bucket-2"
2026-04-30T23:35:44.601877Z  INFO Bucket created. bucket=example-bucket-2
2026-04-30T23:35:44.602008Z  INFO line completed line=3 event="success" exit_code=0 command="create-bucket" raw="create-bucket s3://example-bucket-2"
2026-04-30T23:35:44.602020Z  INFO line started line=4 event="start" command="put-bucket-tagging" raw="put-bucket-tagging --tagging \"team=data&env=prod\" s3://example-bucket-1"
2026-04-30T23:35:44.881342Z  INFO Bucket tagging set. bucket=example-bucket-1
2026-04-30T23:35:44.881455Z  INFO line completed line=4 event="success" exit_code=0 command="put-bucket-tagging" raw="put-bucket-tagging --tagging \"team=data&env=prod\" s3://example-bucket-1"
batch-run: 3 succeeded, 0 failed, 0 warnings, 0 skipped, elapsed 2.5s

The per-subcommand [dry-run] would … info lines are replaced by their concrete counterparts (Bucket created., Bucket tagging set.); everything else — the start / success events, the trailing summary — is the same shape.

Without -v, both the per-line start / success events and the per-subcommand info lines ([dry-run] would …, Bucket created., etc.) are suppressed at the default warn level — only warnings and errors are logged, plus the trailing summary line on stderr. That is why the safety guidance pairs --dry-run with -v: you need info-level output to see what would happen.

Restrictions.

  • Nested batch-run is rejected.
  • cp/mv lines may not use - (stdin/stdout) as source or target.
  • Per-line input is capped at 16 KiB.

Summary. When the run completes (or aborts), an N succeeded, N failed, N warnings, N skipped, elapsed Ts line is written to stderr. Per-line outcomes bucket as: exit 0succeeded; exit 3 or 4 (EXIT_CODE_WARNING, EXIT_CODE_NOT_FOUND) → warnings; exit 130 (the conventional Unix code for SIGINT — returned by per-subcommand cancellation handlers when the user hits Ctrl-C) → skipped (logged at warn level, not error, and never counted toward --max-errors); any other non-zero exit → failed; lines that were never dispatched (fail-fast or --max-errors threshold tripped, or SIGINT) → skipped. Suppress the line with --no-summary. With --json-tracing the same information is emitted as a single-line JSON object instead, e.g. {"summary":"batch-run","succeeded":48,"failed":1,"warnings":2,"skipped":1,"elapsed_seconds":3.4}.

Progress bar. In read-all mode, when stderr is a TTY, a live progress bar is drawn on stderr while the run is in progress. Suppress it with --no-progress — useful when stderr is a TTY but you want machine-readable log output (terminal multiplexers, script(1), some CI runners). Streaming mode and non-TTY stderr already suppress the bar. --json-tracing also suppresses it automatically (the bar would interleave with JSON output). --no-progress and --no-summary are independent — each controls only its own visual element; pass both for fully clean output.

Proxy support

s7cmd respects the standard proxy environment variables (HTTP_PROXY, HTTPS_PROXY) automatically. No flags are required — set the variables in your shell and every subcommand routes its S3 traffic through the proxy.

Proxy authentication is supported via the URL form http(s)://user:password@proxy:port.

Documentation

Each subcommand is documented in the README of its underlying library. For details on flags, semantics, and exit codes, refer to:

Subcommand Documentation
ls s3ls-rs
sync s3sync
clean s3rm-rs
cp, mv, rename, rm, and all others s3util-rs
batch-run s7cmd-only — see the section above

Each of these projects (except batch-run) also ships its own standalone binary, which can be used independently of s7cmd.

Security assumptions

s7cmd is built on a fundamental security assumption: both the object storage system and the specific bucket you operate on must be trusted.

Within this trust model, s7cmd implements the security measures you would reasonably expect of an S3 command-line tool: encrypted transport (TLS/HTTPS) for data in transit, end-to-end integrity verification for transfers (ETag, MD5, SHA256, and CRC checksums), support for server-side encryption, secure handling of credentials through the standard AWS credential providers (with credential environment-variable values hidden from --help output), and — for ls — control-character escaping of the object keys, prefixes, and owner names returned by S3. These measures protect the confidentiality and integrity of your data against transport-level and accidental threats.

However, s7cmd assumes that the storage endpoint is honest and non-adversarial — that it correctly implements the S3 API and returns the data, listings, metadata, and checksum values it actually stores, without tampering. Because subcommands decide what to transfer, list, or delete from the listings and metadata the endpoint reports, the integrity-verification and filtering features are not a defense against a malicious or compromised storage backend that deliberately returns falsified data, fabricated listings, or forged checksums. Against such an adversarial endpoint, these guarantees do not hold.

Crucially, trust must extend to the bucket, not just the storage provider. Even when the object storage system itself is fully trustworthy, a bucket can still be adversarial — for example, a bucket you do not control, a shared bucket writable by others, or one whose objects, metadata, or checksums were crafted by an attacker. If you synchronize from, copy from, list, or delete from such a bucket, the data and metadata it serves are already untrusted at the source, and s7cmd's guarantees no longer apply. A trusted storage provider hosting an untrusted bucket is, for the purposes of this security model, an untrusted source.

Operating on an untrusted, compromised, or non-conformant endpoint or bucket is outside s7cmd's security model. Selecting a trustworthy storage provider, and ensuring that every bucket you operate on is one you control or trust — including its credentials, encryption, and access policies — remains your responsibility.

This mirrors the "Security assumptions" sections of the underlying s3sync / s3util-rs / s3rm-rs / s3ls-rs projects.

Requirements

  • x86_64 Linux (kernel 3.2 or later)
  • ARM64 Linux (kernel 4.1 or later)
  • Windows 11 (x86_64, aarch64)
  • macOS 11.0 or later (aarch64)

All features are tested on the above platforms.

Installation

Download the latest binary from GitHub Releases

You should build ARM64 Windows binaries yourself.

Fully AI-generated, always human-verified

Every line of s7cmd's own source code (including the vendored adaptations from upstream), every test, all documentation, CI/CD configuration, and this README were generated by AI using Claude Code (Anthropic). The same applies to three of the four underlying libraries: s3util-rs, s3ls-rs, and s3rm-rs. The fourth, s3sync, is human-written and serves as the reference architecture from which the AI-generated siblings were derived.

Human verification is a permanent policy, not a one-time event applied only to the initial build. Human engineers authored the requirements, design specifications, and s3sync reference architecture, and continue to review and verify every change to the design, source code, and tests. Every release is manually tested by humans before it ships, and all E2E test scenarios are verified against live AWS S3. No AI-generated change is released without human review and testing — this applies equally to the initial build and to all future updates, including dependency bumps, bug fixes, and new features. The development follows a spec-driven process: requirements and design documents are written first, and the AI generates code to match those specifications under continuous human oversight.

Every underlying library maintains 96%+ automated test coverage. This serves a dual purpose: it verifies that AI-generated code meets its specifications, and it ensures the project remains maintainable by hand — whether because AI tooling becomes unavailable, or because a future maintainer prefers to work without AI assistance. Combined with the modular library design and Apache-2.0 licensing, this means s7cmd can be safely forked and maintained without AI assistance if the need arises.

Discussions about the legitimacy, licensing, or ethics of AI-generated code in general are out of scope for this issue tracker. Issues opened on those grounds — without a concrete, reproducible defect in s7cmd's behavior — will be closed.

Quality verification (by AI self-assessment)

Measurements below are taken at v1.6.0 (commit c454694 on main, tagged 2026-07-20; measured 2026-07-21). The coverage figures are sourced from llvm-cov-report.txt (cargo llvm-cov; lcov.info is the matching machine-readable LCOV artifact) and reflect a single combined run — cargo llvm-cov with RUSTFLAGS="--cfg e2e_test" on the maintainer's machine, 2026-07-20 — so the unit tests, the process-level CLI and batch-run tests, and the live-AWS e2e suite are all included in the report.

Metric Value
Production code ~16,900 lines of Rust across 84 source files in src/
Unit tests (in src/) 477 #[test] / #[tokio::test] annotations
CLI integration tests 692 annotations across 63 files (62 tests/cli_*.rs files plus tests/batch_run.rs); they spawn the real binary with no AWS credentials — S3 interactions, where exercised, hit an in-process loopback mock server; run in CI
E2E integration tests 258 annotations across 28 tests/e2e_*.rs files (gated behind --cfg e2e_test; run only by the maintainer against live AWS)
Code coverage (llvm-cov, combined unit + CLI + e2e run) 97.63% regions (347 / 14,629 missed), 96.94% functions (35 / 1,145 missed), 98.55% lines (151 / 10,448 missed)
Static analysis (clippy) 0 warnings (cargo clippy --all-features)
Formatting 0 diffs (cargo fmt --all --check)
Supply chain (cargo-deny) Clean (cargo deny -L error check); runs on every push and PR in ci.yml and daily at 01:34 UTC in cargo-deny.yml; advisories.ignore = []
Code adapted from the underlying projects CLI frontends vendored from the upstream binaries — src/sync_bin/ (s3sync), src/util_bin/ (s3util-rs), src/clean_bin/ (s3rm-rs), src/ls_bin/ (s3ls-rs) — plus adapted dispatch arms in src/dispatch.rs; the engines themselves are consumed as exact-pinned library dependencies (s3sync 1.60.0, s3util-rs 1.8.0, s3rm-rs 1.4.0, s3ls-rs 1.1.0). src/batch_run/ is s7cmd-original

What these numbers do and do not show:

  • They show what the combined test run exercises — including the live-AWS e2e suite — not how the binary behaves under production load over time. CI asserts only the non-e2e build (unit, CLI, and batch-run tests) on every push and PR, across six build targets; it neither runs the e2e suite nor produces or gates on coverage.
  • Coverage measures s7cmd's own src/ — the wrapper layer: the clap argument surfaces, subcommand dispatch, the vendored CLI frontends, and batch-run. The transfer, listing, and bulk-delete engines are separate crates measured in their own repositories (each underlying library maintains 96%+ coverage, as noted above); their figures are not part of the numbers in this table.
  • Coverage is a structural metric. A covered line can still be incorrect; an uncovered line can still be correct. Use it to size the test surface, not to certify behaviour.
  • The e2e suite covers live-AWS paths (object, bucket-configuration, and stdio round-trips, object annotations, batch-run, dry-run, ctrl-c cancellation, exit codes) but runs only on the maintainer's machine, and reproducing the coverage figures above requires AWS credentials.

The codebase is built through spec-driven development with human review at every step. Test counts and coverage will change as dependencies are updated and refinements land.

AI assessment of safety and correctness (by Claude, Anthropic)

Assessment date: 2026-07-22.

Assessed version: 1.6.0 (branch main, commit c454694, tag v1.6.0).

Method and scope of evidence: this assessment was performed from scratch at v1.6.0, deriving every claim from the source rather than carrying conclusions forward from this README or any earlier assessment. All 84 Rust source files under src/ (16,949 lines) were read in full: the s7cmd-original src/batch_run/ engine, src/dispatch.rs, src/cli.rs and src/main.rs directly line by line, and the four vendored CLI frontends (src/util_bin/, 59 files; src/sync_bin/, src/clean_bin/, src/ls_bin/, 14 files) in full parallel review passes whose safety-relevant claims were re-verified against the cited lines. Also read: the 63 offline process-level test files (tests/cli_*.rs plus tests/batch_run.rs), the 28 live-AWS suites (tests/e2e_*.rs), tests/common/mod.rs, Cargo.toml and Cargo.lock, deny.toml, all five GitHub Actions workflows, Dockerfile, build.rs, .cargo/config.toml, and the coverage artifacts llvm-cov-report.txt / lcov.info (verified internally consistent: identical line and function totals). Where a guarantee lives in an engine crate, the pinned published sources (s3sync 1.60.0, s3util-rs 1.8.0, s3rm-rs 1.4.0, s3ls-rs 1.1.0) were consulted directly. The static checks were re-run during this assessment and confirmed: cargo fmt --all --check clean; cargo clippy --all-features --all-targets zero warnings, also under RUSTFLAGS="--cfg e2e_test" (so the e2e corpus type-checks); cargo deny -L error check fully clean against the advisory database of the assessment date.

Limits of the evidence: this assessment cannot rule out all bugs. It includes no fuzzing, sanitizer or Miri runs, formal verification, or penetration testing. The e2e suite runs only against the maintainer's AWS account; CI neither compiles nor runs it. Coverage measures what the tests execute, not whether the executed code is correct. The transfer, listing and bulk-delete engines are separate crates assessed in their own repositories; they are covered here only as s7cmd consumes them.

Question addressed. s7cmd is a wrapper: 55 of its 56 subcommands hand off to four exact-pinned engine libraries, and the one s7cmd-original engine is batch-run. Three failure modes drive the analysis: (1) the wrapper mis-routes — a subcommand reaches the wrong engine, mistranslates its configuration, or weakens a guarantee (dry-run, exit codes, credential hygiene) that the standalone upstream binary provides; (2) batch-run mis-executes a script — runs the wrong line, loses a failure, kills the whole batch, or copies an inline credential into logs; (3) an operator mistake in one line of a batch mutates the wrong resource with no preview. Each is examined in turn.

Architecture and subcommand surface (v1.6.0)

The Cmd enum has 56 variants (src/cli.rs:270-426): 21 read-only subcommands, 34 mutating ones, and batch-run. Every arm of dispatch returns an i32 instead of exiting (src/dispatch.rs:23), and main delivers it via ExitCode::from (src/main.rs:44-45) — there is no std::process::exit anywhere under src/; the vendoring deliberately removed upstream's process-exiting paths (load_config_exit_if_err for ls/clean, validate_state_flag for the three state-flag put-* commands, re-implemented as non-exiting checks called from dispatch.rs:278,401,418) so a bad line cannot kill a batch. Config-translation failures for sync/ls/clean/cp/mv print a clap-formatted error and return exit 2 (dispatch.rs:28-140). Large subcommand futures are Box::pin-ed to keep dispatch's frame below CI's 2 MB test-thread stacks (dispatch.rs:16-22). Routing is pinned three ways: in-process parse tests over every subcommand string (tests/cli_routing.rs, 62 tests), 63 dispatch unit tests in src/dispatch.rs, and live end-to-end dispatch tests per family.

batch-run (the s7cmd-original engine)

batch-run is where s7cmd adds real machinery, and it is defensively built:

  • Input handling: lines are read with an incrementally enforced 16 KiB cap — a multi-GB line is rejected after one buffer, not buffered (src/batch_run/parser.rs:101-135) — validated as UTF-8, and tokenized with POSIX shlex. Blank/comment lines are skipped; a tokenization error becomes a per-line failure, not an abort.
  • Per-line validation before anything runs: nested batch-run, stdin/stdout operands (cp/mv -, put-bucket-* -, --annotation-payload -), and per-line tracing flags are rejected — the tracing check inspects the parsed clap fields of all ~50 variants, not raw strings, so every accepted spelling is caught (src/batch_run/validate.rs).
  • Invalid lines (tokenize / parse / empty / validate) synthesize exit 2, log at error level with structured fields, and count toward --max-errors / --continue-on-error exactly like runtime failures (src/batch_run/executor.rs:74-92) — a regression test pins that they no longer abort the whole run (tests/batch_run.rs:194-220).
  • Panic containment: every dispatched line runs under catch_unwind; a panicking subcommand becomes exit 101, is logged with the line number and redacted text, and counts toward the failure threshold (executor.rs:125-167). This depends on panic = "unwind" in all build profiles (Cargo.toml:103-113) — a deliberate 1.2.3 change (release-min-size had been panic = "abort", which would have defeated the recovery) and a standing invariant future profile edits must not break.
  • Exit-code aggregation is severity-ranked, not numeric: 1 > 2 > 3 > 4 > any other non-zero > 0 (executor.rs:294-303), so a run mixing exit 1 and exit 130 exits 1. Per-line exit 130 (SIGINT) buckets as skipped and never trips --max-errors (executor.rs:281-287).
  • SIGINT semantics are phased: during read-all's read/validate phases the default handler applies (Ctrl-C kills immediately, nothing has run); the listener is installed only before execution, after which Ctrl-C stops new spawns while in-flight commands cancel via their own handlers (src/batch_run/mod.rs:253-331). A live-AWS test SIGINTs a 200 MB upload mid-batch and asserts exit 130 with the remaining lines skipped (tests/e2e_batch_run.rs:553-645).
  • --parallel is capped at 1024 by a custom clap parser, rejecting at parse time (exit 2) values that previously panicked tokio::sync::Semaphore::new (exit 101) — with the exact panic-boundary value pinned in tests (src/cli.rs:148-170, tests/cli_arg_validation.rs:717-728). Parallel mode drives the non-Send dispatch futures concurrently on a LocalSet with a semaphore bound; the engines' own worker pools provide the actual multi-core parallelism.
  • Line parsing goes through the same post-processed clap tree as the top-level binary (mod.rs:34-37), which disarms the upstream-inherited --auto-complete-shell flag including its AUTO_COMPLETE_SHELL env binding — closing a fixed 1.6.0 bug where the exported variable silently rewrote argument handling (required args lifted, sync/clean paths defaulted) and a per-line cp --auto-complete-shell panicked.

Wrapper fidelity and operator-mistake protection

  • --dry-run exists on all 34 mutating subcommands and none of the 21 read-only ones — both directions asserted by tests (tests/cli_dry_run.rs, 67 tests, including that read-only helps do not expose the flag). Wrapper-level short-circuits return before the mutating call in all 30 thin wrappers; cp/mv/sync/clean enforce dry-run in their engines. Dry-run also forces verbosity to at least info so the [dry-run] line is visible at the default warn level (src/main.rs:62-134). The strongest evidence is live: 34 e2e dry-run tests create real AWS state, run the dry-run, then verify via the SDK that nothing changed (tests/e2e_dry_run.rs).
  • clean — the highest-blast-radius command — keeps its engine's safety gate: dry-run/--force checks, refusal to run non-interactively without --force, and an exact-"yes" confirmation prompt, checked before the Ctrl-C handler or indicator are even wired (src/clean_bin/mod.rs:57-69); at the prompt, Ctrl-C still kills the process via the OS default handler.
  • mv (copy-then-delete) rejects self-moves before any transfer — same bucket, same endpoint, same resolved key, with an explicit --source-version-id allowed as version promotion except the null pseudo-version (src/util_bin/cli/mv.rs:46-98) — and deletes the source only after a four-gate decision tree (no cancellation, no transfer error, no verification warning unless --no-fail-on-verify-error, final cancellation re-check), pinning the delete to the version id the copy actually read (mv.rs:100-162). Every gate is unit-tested against a fake storage that counts delete calls.
  • Object annotations: payloads are read through a hard 1 MiB bounded reader on both upload and download (TOCTOU-free take(cap+1) on upload, streamed cap on download so a hostile endpoint cannot OOM the process; src/util_bin/cli/put_object_annotation.rs:44-61, get_object_annotation.rs:316-333). Uploads send Content-MD5 plus CRC64NVME and fail if the response omits the verification checksum; downloads verify, write to a temp file, re-verify the on-disk bytes, and only then atomically rename — an s7cmd-local hardening over upstream (which renamed first), regression-tested to preserve a pre-existing good file on verification failure (get_object_annotation.rs:181-225).
  • create-bucket --tagging is the one two-call wrapper: a tagging failure after bucket creation warns with the exact partial state and exits 3; no rollback, by design (src/util_bin/cli/create_bucket.rs:83-91).
  • Remaining operator exposure: there is no interactive confirmation outside clean; and the upstream arg structs make positional SOURCE/TARGET (and e.g. POLICY) environment-backed, so an exported TARGET silently supplies the target of a destructive command — including on every batch-run line, which inherits the process environment. A deliberate upstream scripting affordance, but a real foot-gun.

Credential handling

Three independent layers were verified:

  • Upstream types: AccessKeys, SSE-KMS ids and SSE-C keys in all four pinned engine crates derive Zeroize/ZeroizeOnDrop and print ** redacted ** from Debug (verified in the published 1.60.0/1.8.0/1.4.0/1.1.0 sources), so the trace-level config = {:?} lines in the sync/ls/clean dispatch arms cannot print a secret. The cp/mv arm goes further and logs only five enumerated non-secret fields instead of {:?} (src/dispatch.rs:453-465).
  • --help cannot echo env-supplied secrets: hide_credential_env_values re-hides every env-backed *access_key* / *session_token* / *sse_c_key* argument tree-wide as a guard on top of the upstream fixes, enforced by a meta-test that walks every subcommand and requires ≥100 such arguments checked (src/cli.rs:503-517,636-663), plus a process-level test that exports real-shaped values and asserts names render but values never do — with a non-secret control value proving the test cannot pass vacuously (tests/cli_help.rs:611-737).
  • batch-run masks inline credential option values to **** before any line text reaches a log or --json-tracing output, in both --flag value and --flag=value forms, with a whitespace-level fallback so a secret on an unparseable (unbalanced-quote) line is still scrubbed (src/batch_run/redact.rs); masking is asserted end-to-end, including that a presigned URL never contains the secret key (tests/batch_run.rs:1309-1410).

Supply chain, build, and CI

  • The four engine crates are exact-pinned (=1.60.0, =1.8.0, =1.4.0, =1.1.0) to keep the vendored frontend code in sync. Cargo.lock is committed; all release builds and the crates.io publish use --locked.
  • TLS is rustls 0.23.42 with aws-lc-rs 1.17.3 and OS trust anchors; openssl-sys is absent from the lockfile and additionally deny-listed. ring enters the lock only via the e2e-test dev-dependency ureq, not the shipped binary.
  • cargo deny -L error check runs on every push and PR (ci.yml) and daily at 01:34 UTC (cargo-deny.yml); deny.toml sets advisories.ignore = [], denies unknown registries and git sources, and restricts licenses to a permissive allowlist.
  • CI builds and tests six targets with blocking cargo fmt --check, cargo clippy -- -D warnings, and cargo-deny jobs. The release workflow (cd.yml) triggers on version tags, builds --locked, attaches per-artifact SHA-256 checksums and GitHub build-provenance attestations, and publishes to crates.io via OIDC trusted publishing (no long-lived token).
  • The release binary embeds a Lua interpreter (mlua 0.12.0) because s3sync's default features include lua_support for its filter-callback flags; Cargo.toml:14-18 documents this and the opt-out. Intentional, but it is real additional attack surface, and -rdynamic linking on some targets exists solely to support it.
  • Gaps worth naming: the gating clippy job runs without --all-features (the --all-features clippy run is a separate non-gating code-scanning workflow); no CI job compiles the --cfg e2e_test configuration, so the 258-test e2e corpus is type-checked only locally (it does compile warning-free — re-verified in this assessment); CI neither produces nor gates on coverage (the codecov badge is fed from local runs); cd.yml runs no tests itself; the Dockerfile builds without --locked (though it copies the committed lockfile).

Test corpus and coverage measurement

1,427 test annotations total: 477 unit tests in src/ (the batch-run engine and dispatch table are the densest: 63 in dispatch.rs, 48 in executor.rs), 692 offline process-level tests (62 tests/cli_*.rs files plus tests/batch_run.rs — these spawn the real binary; S3 interactions hit a loopback-only mock bound to 127.0.0.1:0 with fake credentials passed as flags, so no AWS account or ambient credentials are touched), and 258 live-AWS tests in 28 tests/e2e_*.rs files, every one gated behind #![cfg(e2e_test)]. Sixteen distinct regression clusters pin previously fixed bugs, including the --parallel semaphore panic, the AUTO_COMPLETE_SHELL re-arming, the mv self-move data loss, credential values in --help, batch-run credential masking, and the mid-batch process::exit kills.

llvm-cov-report.txt (one combined unit + CLI + --cfg e2e_test live-AWS run, produced 2026-07-20 on the maintainer's machine; lcov.info is the same data in LCOV form, verified to carry identical totals) reports 97.63% region, 96.94% function, 98.55% line coverage over 82 files (the two 2-line module stubs contain no executable code). On the paths this assessment cares most about: batch_run/executor.rs 98.69% regions, batch_run/mod.rs 98.39%, batch_run/redact.rs 99.39% regions / 100% lines, batch_run/validate.rs 98.76%, dispatch.rs 98.33%, util_bin/cli/mv.rs 99.21% regions / 100% lines, util_bin/cli/get_object_annotation.rs 97.93%. The weakest files are small error-path wrappers (head_bucket.rs 80.65%, delete_bucket_policy.rs 82.61% regions). Branch coverage is not measured (the branch columns are empty), and coverage remains structural: a covered line can still be incorrect.

Test-harness limits found: the mock server replays canned responses and discards request bodies — only five files assert on captured requests, and only method + path, so a wrapper that sent a wrong body to the right URL would pass offline (the e2e round-trips are what actually verify payloads); roughly 100 tests are smoke-only (help exits 0, missing-args exits 2); sync and mv have no dedicated per-subcommand offline file; and the signal tests use fixed sleeps with bounded polls.

Known limitations and findings of this assessment

This from-scratch pass found no critical defects at v1.6.0. What it did find, ranked:

  • Cancellation exit codes are inconsistent across engines, and clean can mask real errors on Ctrl-C. cp/mv exit 130 on SIGINT, but the vendored sync/ls/clean frontends map user cancellation to exit 0 — and clean's error loop returns success on the first cancellation entry even when genuine deletion failures were recorded alongside it (src/clean_bin/mod.rs:115-118, faithfully vendored from the upstream binary). Consequently a Ctrl-C'd clean that had already hit real errors exits 0, and inside batch-run a Ctrl-C'd sync/clean line is bucketed succeeded while a cp line is bucketed skipped.
  • The mv self-move guard compares endpoints textually (src/util_bin/cli/mv.rs:66-77, documented best-effort): two spellings of the same endpoint on an unversioned bucket re-open the delete-what-you-just-wrote window the guard exists to close. Bucket versioning remains the real backstop.
  • Parallel-executor SIGINT window: the spawn loop checks the interrupt flag before awaiting a semaphore permit (or the channel), so a SIGINT that lands during that await lets one already-queued line spawn afterwards (src/batch_run/executor.rs:399-430,537-577); since its cancellation handler is installed after the signal fired, that line runs to completion rather than cancelling.
  • Severity-ranking edges: any exit code outside 1–4 — including the synthetic panic code 101 — ranks below exit 4, so a --continue-on-error batch mixing a panic with a warning exits 3/4, understating what happened (the error-level panicked log entry is the reliable signal). In streaming mode a reader I/O error raises the exit only to max(code, 1) numerically, so prior warning exits (3/4) survive where the severity rule would surface 1 (src/batch_run/mod.rs:402-412).
  • Wrapper exit-code inconsistencies: rename maps a missing object/bucket to exit 1 where sibling subcommands use 4; the mutating put-*/delete-* wrappers likewise surface NotFound as 1, so scripts cannot distinguish "bucket absent" from other failures on mutations; and mv --no-fail-on-verify-error exits 0 after a verification warning where cp exits 3.
  • Dry-run is not always zero-network: every mutating wrapper builds its SDK client (credential resolution, possibly IMDS/SSO traffic) before the short-circuit, and three paths issue a live read-only call under --dry-runcp --skip-existing (HeadObject), create-bucket --if-not-exists (HeadBucket), and cp --enable-sync-object-annotations (ListObjectAnnotations). No mutation is reachable on any dry-run path (verified per wrapper and asserted by the state-unchanged e2e suite).
  • The nine put-bucket-* configuration bodies and the bucket policy are read with no client-side size cap — a mistyped multi-GB path is fully buffered before S3 rejects it. Batch-run's own 16 KiB line cap does not extend to the files those lines reference.
  • A documented-but-unfixed defect, pinned by its own test rather than hidden: batch-run --streaming - with a stdin pipe that is never closed does not exit on SIGINT alone (tests/cli_sigint.rs:72-80); the reader's blocking stdin read survives the signal until EOF.
  • The vendored frontend files cite older upstream versions (s3sync@1.57.1–1.58.6, s3rm-rs@1.3.3–1.3.4, s3ls-rs@0.4.1, s3util-rs@1.0.0–1.7.1) than the pinned engines they drive; the two 1.8.0-era fixes s7cmd needs are explicitly backported, and the stat-enum matches have no wildcard arms so upstream drift breaks the build rather than passing silently — but this vendored/engine skew is a standing maintenance risk unique to s7cmd's architecture.
  • Smaller items: the annotation download's atomic rename is not preceded by an fsync, so a power loss (not a process crash) can persist the rename without the data; batch-run's structured logs and the trace-level config dumps can include the access key ID (an identifier, never the secret — and batch-run's own masking hides even the ID for inline flags); restore-object --days accepts any positive integer; duplicate tag keys pass client-side validation and are left to S3 to reject.

Is the software reliable?

For readers who are not software engineers: "reliable" here means whether a careful operator can use s7cmd for routine Amazon S3 work without losing data, leaking credentials, or having a batch script do something other than what was written.

Risk: the wrapper corrupts or mis-routes. s7cmd adds no transfer logic of its own — integrity verification, temp-file-then-rename downloads, and version-pinned reads live in the exact-pinned engines, and the wrapper preserves their guarantees (the one place it touches verified bytes, annotation downloads, it re-verifies on disk before the rename — stricter than upstream). Routing from subcommand string to engine call is pinned by three independent test layers.

Risk: a batch run goes wrong. Every line is validated before anything executes (in the default read-all mode, before any line executes); failures — including panics — are contained per line, counted against an explicit threshold, logged with the line number, and rolled into a severity-ranked exit code; inline credentials are masked in every log path; and a 200 MB-upload SIGINT test proves cancellation propagates. The residual sharp edges are the cancellation-exit-code inconsistency and the streaming-stdin SIGINT case above.

Risk: operator mistake. All 34 mutating subcommands take --dry-run, verified live against AWS to change nothing; clean requires --force or an interactive "yes"; mv refuses self-moves; batch-run --check-format validates a whole script without executing it. The exposures that remain are documented and operator-shaped: no interactive confirmation outside clean, and environment-backed positionals that can invisibly supply a target.

What this assessment cannot establish. That the code is bug-free; that the engine crates (assessed separately) are defect-free; or that S3-compatible endpoints behave like Amazon S3. The findings above that reach observable behavior — cancellation exit codes, rename's exit-1, the severity-ranking edges — share a shape worth noting: they mis-report rather than mis-act; no finding of this pass identified a path that mutates or deletes the wrong data absent operator error, with the narrow documented exception of the mv endpoint-spelling window.

In plain terms: at v1.6.0 the failure modes most likely to cause silent harm — a batch that keeps running past failures it should stop on, a credential copied into a log, a wrapper that silently weakens an engine guarantee, a dry-run that mutates — each have a specific, citable, and tested safeguard, and this pass re-verified the fixes for every previously known defect, each pinned by a regression test. The fact that the codebase is AI-generated neither raises nor lowers its reliability; what determines that is the design, the tests, and the verifiable evidence above. The tool remains conditionally reliable: previewing destructive work with --dry-run (and batch-run -v --check-format for scripts), watching exit codes and error-level logs (exit codes alone understate panics and Ctrl-C'd clean runs), keeping bucket versioning for irreplaceable data, and least-privilege IAM are the operating controls the design assumes. Final responsibility for whether the binary fits a given workflow rests with the operator who runs it.

AI assessment of safety and correctness (by Codex)

Assessment date: 2026-07-22.

Assessed tree: main at 839acc3. The Rust implementation and build inputs are identical to v1.6.0 (c454694); the commits after that tag change README content only.

Complete review boundary: all 84 Rust files under src/ were examined in full, including their 477 embedded tests, rather than sampling high-risk modules. The review also covered all 63 offline process-test files, tests/common/mod.rs, all 28 e2e_*.rs suites, Cargo.toml, Cargo.lock, build.rs, .cargo/config.toml, deny.toml, Dockerfile, and all five GitHub Actions workflows. The four exact-pinned engine crates were checked at the interfaces and guarantees on which this wrapper relies; they remain separate dependencies, not source owned by this repository. Conclusions were derived from code and executable evidence, not from the neighboring assessment text.

Coverage evidence: the supplied lcov.info (SHA-256 2da2901598bffa46b6fda6718c815e34701d24870c3e9f9a984a2da1b28bb22a) and llvm-cov-report.txt (SHA-256 f7fa71e841669bbb65673fe311832c7f12752b5218b3c9258a45e7a0053daffb) were parsed independently. Their line and function totals agree exactly.

Limits: this was source review plus deterministic local verification, not formal proof. No fuzzing, Miri, sanitizers, fault injection, penetration test, or fresh live-AWS run was performed. The E2E configuration was compiled and linted, while its prior live execution is represented only by the supplied coverage artifacts.

Bottom line

No critical vulnerability was found in s7cmd's own code, and the ordinary Amazon S3 paths show strong defensive engineering. The wrapper has complete dispatch coverage, contains per-line batch failures instead of terminating the process, protects credential values in help and batch logs, and preserves dry-run and transfer-integrity controls. There is no unsafe block in production code and no production subprocess execution or shell evaluation; batch-run parses and dispatches commands in-process.

I would nevertheless describe v1.6.0 as conditionally reliable, not safety-certified. One narrow mv case can still delete an object, and batch input/cancellation behavior has availability and result-reporting defects. These are concrete open findings, not hypothetical objections to AI-generated code.

Open findings, ordered by operational impact

Impact / priority Finding Consequence and boundary
High impact, narrow trigger The mv self-move guard compares source and target endpoint strings literally (src/util_bin/cli/mv.rs:65-77). Equivalent spellings of the same service therefore bypass the guard. With the same bucket and resolved key on an unversioned or version-suspended bucket, copy-then-delete can delete the object just written. The operator must explicitly construct this endpoint mismatch; bucket versioning is the strongest mitigation. Endpoint canonicalization or an explicit same-service override would close it.
Moderate Batch memory is not bounded in aggregate. Default mode collects every parsed line in a Vec (src/batch_run/parser.rs:60-88), while streaming mode feeds an unbounded_channel (src/batch_run/mod.rs:361). The 16 KiB per-line limit prevents a single-line allocation attack but not a huge script, and a fast streaming producer can outpace a slow S3 command until memory is exhausted. A total-line/byte cap and a bounded channel would make the resource guarantee real.
Moderate Cancellation is reported inconsistently. ls maps cancellation to 0 (src/ls_bin/mod.rs:75-78); sync can complete a cancelled pipeline without a distinct cancellation status; and clean returns 0 as soon as it encounters any cancellation error, even if the same collected error set contains real deletion failures (src/clean_bin/mod.rs:98-124). Automation and batch-run can classify interrupted work as success. In clean, a genuine error may be masked. Error aggregation should retain real errors and use one cancellation code, preferably 130, across engines.
Moderate Streaming stdin is not promptly cancellable. After fail-fast or SIGINT the executor drains the channel and awaits its closure, then run_streaming awaits the reader (src/batch_run/executor.rs:484-505,579-582; src/batch_run/mod.rs:399-412). Tokio's stdin read can remain blocked until the producer closes the pipe. batch-run --streaming - can hang after SIGINT or an early failure when stdin stays open. The process test explicitly documents that EOF is load-bearing (tests/cli_sigint.rs:68-105). A cancellable reader design or documented external pipe closure is required.
Low to moderate Parallel stop checks occur before awaiting the next channel item or semaphore permit, with no re-check after the await (src/batch_run/executor.rs:398-430,537-577). A SIGINT or reached error threshold can allow one already-queued command to start afterward. Because that command installs its own handler after the signal, it may run to completion.
Low to moderate Exit aggregation understates some failures. All nonstandard codes, including caught panic 101, rank below warning 3 and not-found 4 (src/batch_run/executor.rs:289-313); streaming reader failure uses numeric max(code, 1) (src/batch_run/mod.rs:402-411). A batch containing a panic plus a warning can exit 3/4, and a reader error after a warning can retain the warning code. Error-level logs remain accurate, but exit-code-only automation is not.
Low, availability Nine JSON file/stdin inputs are read with unbounded read_to_string before parsing or sending: policy, CORS, encryption, lifecycle, logging, notification, replication, website, and public-access-block. A mistaken or hostile multi-gigabyte input can exhaust memory. The annotation payload paths correctly demonstrate the bounded-read pattern and should be reused.

Additional correctness edges are smaller but real. rename reports missing source/bucket as general error 1 while many sibling commands use not-found 4 (src/util_bin/cli/rename.rs:52-58). mv --no-fail-on-verify-error can delete the source and return success despite a verification warning, whereas cp returns warning 3. Annotation file output flushes, re-reads, verifies, and atomically renames, but does not sync_all, so its promise covers process crashes and detected corruption rather than power-loss durability (src/util_bin/cli/get_object_annotation.rs:181-224). Positional arguments inherited from the engine CLIs are environment-backed, so exported SOURCE, TARGET, or policy variables can silently fill destructive command arguments. There is no confirmation prompt outside clean.

--dry-run prevents mutations, but it is not a no-I/O or no-network guarantee. Client construction happens first and may perform credential-provider work. sync, clean, and transfer planning can enumerate or inspect S3 state; thin-wrapper examples include cp --skip-existing, annotation synchronization, and create-bucket --if-not-exists, which perform read-only S3 checks before deciding what they would do (src/util_bin/cli/cp.rs:17-33; src/util_bin/cli/create_bucket.rs:45-64). This is safe with respect to S3 state but matters in isolated environments and when metadata/SSO credential providers are enabled.

Safety controls that held under full-source review

  • The 56-variant command enum has a corresponding non-exiting dispatch path. Configuration failures return 2 instead of invoking upstream clap::Error::exit, so one bad batch line cannot terminate its siblings (src/dispatch.rs). The large transfer futures are boxed to avoid known small-stack overflows.
  • All 34 mutating command surfaces expose --dry-run. Thin wrappers return before their mutation calls, while cp, mv, sync, and clean carry the flag into their pinned engines. The live E2E corpus contains a state-unchanged case for every mutating command. Read-only setup and planning can still contact S3 as described above.
  • mv normally has a sound deletion decision tree: no delete after cancellation, copy error, or verification warning without the explicit override; it checks cancellation again immediately before delete and pins deletion to the source version actually read (src/util_bin/cli/mv.rs:100-161). The endpoint-equivalence gap above is before that tree, not a failure of those gates.
  • Annotation upload and download enforce the 1 MiB limit with bounded streaming reads. Upload sends Content-MD5 and CRC64NVME and verifies the returned checksum; download verifies content length plus available checksums, rejects unsupported returned algorithms cleanly, writes beside the target, re-verifies the saved bytes, then atomically persists. A pre-existing destination survives verification failure (src/util_bin/cli/put_object_annotation.rs:44-100; get_object_annotation.rs:87-224,315-394).
  • batch-run enforces a 16 KiB line limit incrementally, rejects invalid UTF-8, tokenizes with shlex, rejects nested batches, stdin/stdout conflicts and per-line tracing, caps --parallel at 1024, and converts parse/validation errors into per-line exit 2. Dispatch is wrapped in catch_unwind, and all build profiles retain panic = "unwind", so a subcommand panic becomes a recorded exit 101 instead of tearing down the batch (src/batch_run/; Cargo.toml:103-113).
  • Credential values are hidden from env-aware help output across the complete clap tree (src/cli.rs:486-517). Batch log redaction handles both --flag value and --flag=value, including the malformed-quoting fallback (src/batch_run/redact.rs). The pinned engine credential types redact Debug output and zeroize secret fields, so the remaining trace-level full-config logs do not print secret keys or session tokens. Access-key identifiers may remain partially visible by design.
  • Production source contains no direct unsafe, no shell invocation, and no process-spawning API. Lua support is inherited from s3sync's default features and deliberately expands the binary's attack surface, but Lua runs only operator-supplied filter code rather than data received from S3.

Tests, coverage, and what the numbers mean

The supplied report totals are 97.63% regions (14,282/14,629), 96.94% functions (1,110/1,145), and 98.55% lines (10,297/10,448). There are no branch records: both artifacts report zero measured branches. The line and function figures are internally consistent, but two qualifications prevent treating 98.55% as production-only coverage:

  1. lcov.info includes functions named under ::tests:: and line records from #[cfg(test)] modules—for example, the mv fake storage and tests above line 900. The 10,448-line denominator therefore mixes product and test implementation. Similarly, the README's 16,949 physical lines under src/ include the 477 embedded tests; they are not all production lines.
  2. The loopback mock records method and request target, then drains request bodies without retaining them and exposes no captured request headers (tests/common/mod.rs:200-259). Most offline wrapper tests therefore prove parsing, routing, status mapping, and response handling, but not exact outbound JSON or signed headers. Live-AWS round trips provide the stronger payload evidence and are not a CI gate.

The corpus is broad: 477 embedded test annotations, 692 annotations in 63 offline process-test files, and 258 annotations in 28 gated live-AWS files. Some tests are explicitly coverage-oriented rather than behavioral—for example, local sync tests ignore run's result—so count and coverage must be read alongside assertion quality.

During this assessment, these commands passed without warnings or failures:

  • cargo fmt --all --check
  • cargo test --all-features --locked
  • cargo clippy --all-features --all-targets --locked -- -D warnings
  • RUSTFLAGS="--cfg e2e_test" cargo clippy --all-features --all-targets --locked -- -D warnings
  • cargo deny -L error check (advisories, bans, licenses, and sources all clean)

The E2E tests were compiled by the second clippy run but not executed because doing so mutates a configured AWS account. Default cargo test correctly sees those gated files as zero-test targets.

Dependency and delivery assessment

The four engine crates are exact-pinned and the lockfile is committed. Release and publish jobs use --locked, produce SHA-256 files, and attest release archives. The resolved HTTP stack uses rustls 0.23.42; openssl-sys is absent and explicitly denied. Cargo-deny rejects unknown registries and git sources and has no ignored advisories.

The remaining supply-chain weaknesses are conventional rather than runtime defects: most GitHub Actions are referenced by mutable major-version tags rather than commit SHAs; CI follows moving stable; the gating clippy job omits --all-features --all-targets; CI does not compile the e2e_test configuration or gate on coverage; release jobs do not rerun tests; and the Dockerfile uses mutable base tags and builds without --locked. These do not invalidate the tested source tree, but they weaken reproducibility and increase the trust placed in workflow dependencies.

Reliability conclusion

For routine, supervised Amazon S3 use, the evidence supports the tool's core claims: routing is exhaustive, mutation previews exist everywhere, credential handling is deliberate, transfer deletion is normally gated, annotation bytes receive unusually strong integrity treatment, and failures generally become structured nonzero results. I found no path in the ordinary same-endpoint flow that silently mutates the wrong S3 resource, no designed logging path that emits credential secrets, and no dry-run path that invokes an S3 mutation.

For unattended destructive automation, the conditions matter. Canonicalize and keep source/target endpoints identical for same-service mv, enable bucket versioning for valuable data, bound or trust batch inputs, close streaming stdin on cancellation, and monitor error logs in addition to the final exit code. Until the mv endpoint comparison, aggregate batch bounds, and cancellation/error aggregation are fixed, those controls are part of the safety model rather than optional operational advice.

AI assessment of safety and correctness (by Gemini)

Assessment date: 2026-07-22.

Assessed version: 1.6.0 (branch main, commit 837518b, tagged v1.6.0 at c454694).

Method and scope of evidence: This assessment was performed completely from scratch for v1.6.0, independently reviewing the entire codebase without relying on prior summaries. All 84 Rust source files under src/ (16,949 physical lines) were systematically inspected in full, including the s7cmd-original src/batch_run/ engine (8 modules), top-level CLI parsing and dispatch (src/main.rs, src/cli.rs, src/dispatch.rs), and all four vendored CLI frontends (src/util_bin/, src/sync_bin/, src/clean_bin/, src/ls_bin/, total 73 files). Additionally, all 63 offline process-level test suites (tests/cli_*.rs and tests/batch_run.rs), 28 live-AWS E2E test suites (tests/e2e_*.rs), test harness helpers (tests/common/mod.rs), build configuration (Cargo.toml, Cargo.lock, build.rs, .cargo/config.toml), security policies (deny.toml), Dockerfile, and all five GitHub Actions workflows were examined. Code coverage artifacts llvm-cov-report.txt and lcov.info were verified for internal consistency (line and function totals match exactly). Local verification commands were run from scratch and confirmed clean: cargo fmt --all --check zero diffs; cargo clippy --all-features --all-targets --locked -- -D warnings zero warnings; RUSTFLAGS="--cfg e2e_test" cargo clippy --all-features --all-targets --locked -- -D warnings zero warnings; cargo test --all-features --locked 477 unit tests passed; cargo deny -L error check clean with zero advisories ignored. Interfaces to the four exact-pinned upstream engine crates (s3sync = "=1.60.0", s3util-rs = "=1.8.0", s3rm-rs = "=1.4.0", s3ls-rs = "=1.1.0") were audited.

Limits of evidence: This review constitutes static code audit and deterministic local test execution. It does not include formal mathematical proof, fuzzing, Miri execution, memory/thread sanitizers, or live AWS network mutation runs. E2E tests are gated under cfg(e2e_test) and run against maintainer AWS infrastructure; coverage measures code execution paths, not absolute logical correctness under arbitrary cloud edge cases.

Question addressed. As a CLI wrapper and batch runner for Amazon S3, s7cmd delegates 55 of its 56 subcommands to four exact-pinned engine crates while providing one custom engine (batch-run). This evaluation examines whether s7cmd introduces routing errors, credential leaks, unhandled panics, uncontained batch failures, data destruction during transfers, or unexpected mutation behaviors during dry runs.

Command Dispatch & Structural Non-Exiting Guarantee

  • Complete Subcommand Routing: The Cmd enum defines 56 variants (src/cli.rs:270-426): 21 read-only subcommands, 34 mutating subcommands, and batch-run. src/dispatch.rs maps every variant to its underlying logic. Routing correctness is validated across 62 subcommand parsing tests (tests/cli_routing.rs), 63 unit tests in src/dispatch.rs, and E2E suites.
  • Process Stability & Non-Exiting Contract: Production code under src/ contains zero calls to std::process::exit. Every dispatch branch returns a numeric status (ExitStatus or i32), which main() converts to std::process::ExitCode (src/main.rs:44-45). In the vendored frontends, upstream process-exiting calls (load_config_exit_if_err, state flag validation exits) were deliberately refactored into non-exiting status returns (dispatch.rs:278,401,418). A configuration or parameter error in one subcommand returns exit 2 without terminating the parent process or killing a batch-run sequence.
  • Stack Memory Protection: Large subcommand future types are explicitly Box::pin-ed (src/dispatch.rs:16-22) to keep dispatch stack frames well under the 2 MB stack limit of test worker threads.

batch-run Engine Architecture & Fault Isolation

batch-run represents s7cmd's original execution engine, built with multi-layered defensive controls:

  • Incremental Line Buffering: Input lines are read using read_line_capped (src/batch_run/parser.rs:101-135), which enforces a strict 16 KiB limit (MAX_LINE_LEN) incrementally via BufRead::fill_buf. Pathological multi-gigabyte single-line inputs are aborted after buffering ~16 KiB rather than exhausting process memory. UTF-8 validation and POSIX shell tokenization (shlex) are applied to every line.
  • Pre-Execution Validation: src/batch_run/validate.rs validates parsed argument structures before running commands. It explicitly rejects nested batch-run invocations, stdin/stdout dash operands (-), and per-line tracing/verbosity flags (-v, --tracing-log-format). Validation failures synthesize exit 2 and count toward --max-errors / --continue-on-error thresholds rather than aborting the batch.
  • Panic Boundary Containment: Every subcommand execution is wrapped in futures::FutureExt::catch_unwind (src/batch_run/executor.rs:125-167). Any unexpected panic inside a subcommand is caught, logged with line numbers and redacted text, assigned exit code 101, and counted toward error limits. This mechanism relies on panic = "unwind" specified across all build profiles in Cargo.toml:103-113.
  • Severity-Ranked Exit Codes: Batch exit status is determined by severity ranking rather than simple maximum value: exit 1 (error) > exit 2 (arg/validation error) > exit 3 (warning) > exit 4 (not found) > other non-zero > exit 0 (src/batch_run/executor.rs:294-303). Per-line SIGINT (exit 130) is bucketed as skipped (executor.rs:281-287) and does not trip error thresholds.
  • Phased Signal Handling: Signal listeners are not installed during the script reading/validation phase (where Ctrl-C terminates immediately). The SIGINT handler is registered only before command execution starts, ensuring in-flight futures handle cancellation cleanly while preventing new commands from spawning (src/batch_run/mod.rs:253-331).
  • Parallel Execution Safety: --parallel concurrency is constrained to [1, 1024] by a custom clap parser (src/cli.rs:148-170), preventing semaphore allocation panics. Tokio LocalSet drives async execution with a concurrency semaphore.
  • Shell Auto-Completion Isolation: Top-level --auto-complete-shell is disarmed on subcommands (src/main.rs:26-34), preventing inherited environment variables from altering subcommand argument parsing.

Operator Safeguards, Dry-Run Integrity & Transfer Safety

  • Comprehensive Dry-Run Coverage: All 34 mutating subcommands accept --dry-run; none of the 21 read-only subcommands accept it (tests/cli_dry_run.rs). Thin wrappers abort before invoking mutating S3 API calls, while complex operations (cp, mv, sync, clean) propagate --dry-run into their underlying engine. Dry-run automatically elevates minimum logging verbosity to info level (src/main.rs:62-134) so [dry-run] execution logs are visible. 34 E2E live-AWS tests verify that dry-run calls leave cloud resources unmodified (tests/e2e_dry_run.rs).
  • High-Risk Delete Protection: clean (bulk delete) mandates --force or interactive "yes" confirmation (src/clean_bin/mod.rs:57-69). Interrupted prompts exit via default OS signal handling.
  • mv Copy-Then-Delete Decision Tree: mv checks for self-move conditions (src/util_bin/cli/mv.rs:46-98) by comparing source and target buckets, endpoints, resolved keys, and version IDs before executing any transfer. Deletion of the source object is guarded by a 4-gate decision tree (mv.rs:100-162): (1) no cancellation during transfer, (2) successful copy completion, (3) no checksum/verification warnings (unless --no-fail-on-verify-error), (4) final cancellation token re-check immediately prior to delete. Source deletion specifies the exact version ID read during copy.
  • Object Annotation Integrity: Annotation payloads enforce a 1 MiB limit (src/util_bin/cli/put_object_annotation.rs:44-61, get_object_annotation.rs:316-333). Uploads verify Content-MD5 and CRC64NVME response checksums. Downloads stream bytes into a temporary file (tempfile), verify on-disk payload checksums, and perform an atomic filesystem rename only after successful verification (get_object_annotation.rs:181-225). Pre-existing destination files remain intact if verification fails.
  • Partial State Warnings: create-bucket --tagging executes bucket creation followed by tagging. If tagging fails after bucket creation, it emits a warning detailing partial state and exits 3 (src/util_bin/cli/create_bucket.rs:83-91).

Credential Hygiene & Masking

  • Engine Credential Redaction: Access key structs and SSE encryption keys in engine crates derive Zeroize/ZeroizeOnDrop and implement Debug formatting returning ** redacted **. Detailed config dumps in cp/mv dispatch limit logged fields to non-sensitive metadata (src/dispatch.rs:453-465).
  • Help Output Protection: hide_credential_env_values (src/cli.rs:503-517,636-663) recursively removes default environment variable secret values from --help text across all subcommands. Process-level tests (tests/cli_help.rs:611-737) confirm credential flag names display without leaking environment variable contents.
  • Batch Log Redaction: src/batch_run/redact.rs sanitizes inline credentials (access keys, secret keys, session tokens, presigned URLs) in both --flag value and --flag=value syntax before logging or writing JSON trace records. Unparseable lines use fallback regex/whitespace scrubbing (tests/batch_run.rs:1309-1410).

Supply Chain & Build Pipeline Security

  • Pinned Engine Dependencies: Cargo.toml pins engine dependencies to exact versions (s3sync = "=1.60.0", s3util-rs = "=1.8.0", s3rm-rs = "=1.4.0", s3ls-rs = "=1.1.0"). Cargo.lock is committed; build and publication workflows enforce --locked.
  • TLS & Crypto Stack: Cryptographic transport uses rustls 0.23.42 with aws-lc-rs 1.17.3 and OS trust anchors. openssl-sys is excluded from the dependency tree and explicitly banned in deny.toml. ring is restricted to ureq test dependencies.
  • Dependency Auditing: cargo deny -L error check runs on all pushes/PRs (ci.yml) and daily schedules (cargo-deny.yml). deny.toml maintains advisories.ignore = [] and enforces license allowlists.
  • Release Provenance: Release builds (cd.yml) compile with --locked, produce SHA-256 digests, generate GitHub Actions build provenance attestations, and publish to crates.io via OIDC trusted publishing.
  • Embedded Lua Interpreter: s3sync includes lua_support (mlua 0.12.0) by default to support Lua filter callbacks (Cargo.toml:14-18). While intentional, embedding Lua increases binary attack surface.

Test Corpus & Coverage Evidence

s7cmd includes 1,427 test annotations across three tiers:

  1. Embedded Unit Tests: 477 tests in src/ (covering dispatch.rs, executor.rs, redact.rs, mv.rs, get_object_annotation.rs, etc.).
  2. Offline Integration Tests: 692 tests across 63 files (tests/cli_*.rs and tests/batch_run.rs) executing CLI binaries against an in-process loopback mock server (127.0.0.1:0).
  3. Live AWS E2E Suites: 258 tests across 28 files (tests/e2e_*.rs) gated behind cfg(e2e_test).

llvm-cov-report.txt and lcov.info present combined test coverage (unit + CLI + live-AWS E2E run):

  • Line Coverage: 98.55% (10,297 / 10,448 executable lines; 151 missed)
  • Function Coverage: 96.94% (1,110 / 1,145 functions; 35 missed)
  • Region Coverage: 97.63% (14,282 / 14,629 regions; 347 missed)
  • Branch Coverage: Not measured by standard llvm-cov instrumentation in Rust.

Core module coverage highlights: batch_run/executor.rs (98.73% lines), batch_run/mod.rs (98.87% lines), batch_run/redact.rs (100% lines), util_bin/cli/mv.rs (100% lines), util_bin/cli/get_object_annotation.rs (99.26% lines).

Identified Technical Findings & Operational Limitations

  1. Non-Canonical Endpoint Comparison in mv Self-Move Guard: check_not_self_move (src/util_bin/cli/mv.rs:67-77) compares endpoint URLs using string equality (source_endpoint != target_endpoint). Syntactically distinct spellings of the same S3 endpoint (e.g., HTTP vs HTTPS, IP vs hostname) bypass the self-move check, relying on bucket versioning to prevent data loss.
  2. Unbounded Aggregate Batch Memory: While single lines are capped at 16 KiB, standard batch-run buffers all parsed lines in a Vec (src/batch_run/parser.rs:61), and --streaming mode uses an unbounded_channel (src/batch_run/mod.rs:361). Scripts with millions of lines can consume substantial memory.
  3. Inconsistent Cancellation Exit Reporting: Cancellation handling varies by subcommand: ls maps cancellation to exit 0 (src/ls_bin/mod.rs:75-78); clean returns exit 0 upon encountering cancellation even if earlier object deletion errors occurred (src/clean_bin/mod.rs:98-124); sync maps cancellation to exit 0.
  4. Streaming Stdin Cancellation Delay: In batch-run --streaming -, reading from an open stdin pipe can remain blocked on Tokio's stdin reader after SIGINT or early error failure until EOF is received (tests/cli_sigint.rs:72-80).
  5. Parallel Executor Queue Signal Window: In parallel execution, the interrupt check occurs before awaiting a semaphore permit (src/batch_run/executor.rs:399-430). A SIGINT arriving during permit await can allow one previously queued command to start.
  6. Severity Code Aggregation Edge Cases: Exit codes outside 1–4 (including panic exit 101) rank below exit 4 in batch severity ranking (src/batch_run/executor.rs:289-313), causing a batch containing a panic and a warning to exit 3 or 4.
  7. Uncapped JSON Configuration Reads: Nine bucket configuration commands (policy, CORS, lifecycle, encryption, website, logging, notification, replication, public-access-block) read input files using unbounded read_to_string, unlike annotation payloads which enforce a 1 MiB cap.
  8. Network Activity During Dry Runs: Subcommands with --dry-run perform client configuration and read-only S3 checks (e.g. cp --skip-existing issuing HeadObject, create-bucket --if-not-exists issuing HeadBucket) before suppressing mutating calls.

Reliability Summary

s7cmd exhibits robust defensive engineering for Amazon S3 management. Subcommand routing is complete, non-exiting dispatch guarantees process persistence during batch runs, panic boundaries prevent process crashes, credential redaction is systematically implemented, and object annotation workflows provide strong atomic verification.

The binary is conditionally reliable:

  • Destructive operations should be previewed using --dry-run (and batch-run --check-format for batch scripts).
  • S3 bucket versioning should be enabled for critical datasets.
  • Automated workflows should monitor structured error logs in addition to numeric exit status.
  • Source and target endpoints in mv scripts should use canonical, identical strings.

Scope

s7cmd is designed to cover Amazon S3 object operations and bucket management — listing (ls), single- and bulk-object transfers (cp / mv / rm), atomic server-side rename (rename), recursive synchronization (sync), bulk delete (clean), archive restoration (restore-object), pre-signed URL generation (presign), and the common bucket-level configurations (tagging, versioning, policy, policy-status, lifecycle, encryption, CORS, public-access-block, website, logging, notification, replication, transfer acceleration, request payment). For any S3 use case outside that scope, use a more comprehensive tool such as the AWS CLI (aws s3api).

s7cmd targets Amazon S3 as its only supported platform. S3-compatible storage (MinIO, Cloudflare R2, Backblaze B2, Wasabi, Ceph RGW, DigitalOcean Spaces, IBM COS, and similar) is provided strictly as-is, with absolutely no support or assistance — such services may work via --endpoint-url (and --source-force-path-style / --target-force-path-style when path-style addressing is required), but they are not part of the official test matrix and behavior may change between releases. This is a structural consequence of building on aws-sdk-rust, which is generated from AWS service models and assumes Amazon S3 semantics (checksum headers, endpoint resolution, signing variants, response schemas); features that depend on AWS-specific semantics, such as CRC64NVME checksums or newer S3 API additions, may not work against non-AWS endpoints. Bug reports, questions, and assistance requests regarding S3-compatible storage will not be addressed.

s7cmd is not intended to be a drop-in replacement for, or behaviorally compatible with, any other S3 client — including the AWS CLI (aws s3, aws s3api) and tools such as s3cmd, s4cmd, s5cmd, s6cmd, and rclone. Its command-line flags, transfer semantics, verification rules, and exit codes are designed around the underlying libraries' own scope and design principles — not interoperability with another tool's interface. Output formats and flag names will not be adjusted to match any external tool, and scripts written against another S3 client should not be expected to work with s7cmd unmodified. The numeric progression in the name (s3cmds4cmds5cmds6cmds7cmd) does not imply succession or compatibility.

Non-Goals

The following are explicitly out of scope and will not be added, regardless of demand:

  • Support, testing, or guaranteed compatibility for any storage service other than Amazon S3. S3-compatible storage is provided strictly as-is, with no support or assistance, as described in the Scope section above; adding dedicated code paths, provider-specific workarounds, or backends for services such as MinIO, Cloudflare R2, Backblaze B2, Wasabi, Ceph RGW, DigitalOcean Spaces, IBM COS, Tencent COS, Alibaba OSS, Azure Blob Storage, or Google Cloud Storage is out of scope.
  • Feature parity with, or porting features from, other S3 clients. Feature requests of the form "tool X has feature Y, please add it to s7cmd" — including variants such as "feature Y would also be useful in s7cmd," "many users expect Y because tool X has it," or "Y is missing compared to tool X" — will be closed without further discussion. The existence of a feature, flag, command, output format, or behavior in aws s3, aws s3api, s3cmd, s4cmd, s5cmd, s6cmd, rclone, or any other S3 tool carries no weight in s7cmd's design decisions, regardless of how the request is framed. Each feature is evaluated solely against s7cmd's own scope and the design principles of its underlying libraries. If the feature you need exists in another tool, use that tool.
  • Outperforming other S3 tools on raw speed or memory usage. Performance and resource consumption are addressed only when they compromise practical workflows — not for edge cases or benchmark wins. Issues of the form "tool X transfers Y MB/s faster", "tool X transfers Y objects/second faster", or "tool X uses less RAM than s7cmd in benchmark Z" will be closed. If raw throughput is your top criterion, use a tool optimized for it.
  • FUSE filesystem mounting, daemon mode, or any persistent background process. s7cmd is a one-shot CLI; it runs, transfers, and exits.
  • Workflow orchestration features — scheduling, cross-run state databases, retry queues that survive process restart, or DAG execution. Use a workflow engine such as Airflow, Argo Workflows, or AWS Step Functions for orchestration.
  • A graphical user interface, a TUI, or an interactive shell mode.
  • A plugin or extension mechanism.
  • AWS service coverage beyond S3. s7cmd will not add subcommands for IAM, KMS, CloudFront, or any other AWS service, even when they interact closely with S3.
  • Edge cases that are more reasonably addressed by using the AWS SDK directly, shell scripting, or other purpose-built tooling. s7cmd is not intended to cover every conceivable S3 use case; niche or one-off requirements that can be straightforwardly handled by combining the AWS SDK, shell pipelines, or existing tools fall outside its scope.
  • Changes to flag names, subcommand names, default values, output formats, log formats, or exit code assignments based on subjective preference. Such interfaces are stabilized once shipped; breaking changes are made only when required by an underlying library, an upstream SDK, or a clear correctness bug.
  • Additional platform targets, distribution channels, or package manager registrations beyond those listed in Requirements and Installation. Community-maintained packages are welcome but will not be endorsed or supported.

Issues and pull requests requesting any of the above will be closed.

Maintenance Model

s7cmd is maintained as a personal project. Dependency updates and critical bug fixes are applied on a best-effort basis. New features are not actively solicited. If you need guaranteed enterprise support, this is not the tool for you.

Intended Audience and Issue Tracker Scope

s7cmd assumes operational familiarity with Amazon S3 and the AWS SDK. It is aimed at engineers who already run S3 workloads — not at learners or general AWS users.

The issue tracker accepts:

  • Reproducible defects in s7cmd's own behavior (with version, exact command, and observed vs. expected output).
  • Scope-aligned feature discussion, subject to the Non-Goals section above.

The issue tracker does not accept:

  • General questions about S3, IAM, AWS credentials, or AWS account configuration. See the AWS documentation.
  • Usage questions about other S3 clients.
  • Help with user shell scripts, pipelines, or CI configurations that do not isolate an s7cmd-specific defect.
  • Tutorials or design consulting.
  • Diagnosing or fixing performance degradation, resource exhaustion, or errors caused by raising concurrency settings.
  • Questions and issues that belong with AWS, with the operator of an S3-compatible storage service, or with the operating system vendor rather than with s7cmd — including general S3, IAM, KMS, networking, and account-configuration questions; S3 (or S3-compatible) service behavior such as request rate limits, 503 SlowDown, consistency semantics, or regional availability; operating-system configuration and behavior such as ulimit and file-descriptor limits, kernel networking parameters, filesystem quirks, shell quoting, path-length limits, code signing, or antivirus interference; and anything that reproduces with the AWS CLI, the AWS SDK, or the vendor's own client directly. Refer to the AWS documentation, AWS Support, your storage vendor's documentation, or your OS vendor's documentation. If the issue is not specific to s7cmd's own code, it belongs there, not here.

Out-of-scope issues will be closed without further discussion.

Contributing

  • Bug reports are welcome, but responses are not guaranteed.
  • Since this project is considered functionally complete, I will not accept any feature requests.
  • If you find this project useful, feel free to fork and modify it as you wish.

🔒 I consider this project “complete” and will maintain it only minimally going forward. However, I intend to keep the AWS SDK for Rust and other dependencies up to date monthly.

Issue and PR lifecycle

To keep the tracker focused, an issue or PR with no activity for 30 days is labeled stale and closed 7 days later unless a new comment (or, for PRs, a new commit) is added. Items labeled pinned or security are exempt; PRs are also exempt from pinned. Closed items can always be reopened.

License

Apache-2.0