# aws-js-s3-explorer: a single index.html that renders a public S3 bucket

> AWS JavaScript S3 Explorer is one HTML file holding the whole application, and it browses public buckets only. The configuration it needs is a bucket policy, and the path you use to reach the file decides whether you also need CORS.

**awslabs/aws-js-s3-explorer** — AWS JavaScript S3 Explorer is a JavaScript application that uses AWS's JavaScript SDK and S3 APIs to make the contents of an S3 bucket easy to browse via a web browser. 

- Repository: https://github.com/awslabs/aws-js-s3-explorer
- Stars: 861 · Forks: 283
- Language: HTML
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/awslabs-aws-js-s3-explorer

## The whole application is one file you copy into a bucket

AWS JavaScript S3 Explorer is a JavaScript application that uses the AWS JavaScript SDK and the S3 APIs to make the contents of a public S3 bucket easy to browse from a web browser. The project describes its purpose plainly: easier sharing of public objects and data via Amazon S3.

What makes it unusual is the packaging. The index.html file contains the entire application. There is no build step, no bundle and no server. You put that file in a bucket, and a visitor who opens it is prompted to enter the name of an S3 bucket, after which the contents of that bucket are rendered on the page. The visitor chooses the bucket, which is why the application can live in any bucket and work against any other.

The repository is correspondingly small. The top level holds LICENSE, NOTICE.txt, README.md, index.html and a screenshots/ directory, and the README shows three screenshots of the running tool: the default starting view, the view of all objects in a folder, and the view of all objects in a bucket.

One historical note matters if you find the project mentioned elsewhere. The README says the v2-alpha branch, which supported non-public S3 buckets, has been retired, and points to issue 148. The version you get today browses public buckets only.

## Making a bucket browsable is a bucket policy, not application setup

There is nothing to configure inside the explorer. The whole setup happens in the S3 console, and it starts with the Permissions tab and the Bucket Policy editor on the bucket you want to share. The README gives the policy to paste, with BUCKET-NAME replaced by your bucket name:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicListGet",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "s3:List*",
        "s3:Get*"
      ],
      "Resource": [
        "arn:aws:s3:::BUCKET-NAME",
        "arn:aws:s3:::BUCKET-NAME/*"
      ]
    }
  ]
}
```

Read the actions rather than skipping them. s3:List* grants listing, which is what makes a bucket browsable rather than merely readable file by file. s3:Get* grants retrieval of objects. What the policy does not grant is just as important: the README states that it allows anyone to list the bucket and get any file from it, and that it will not allow them to upload, modify or delete files.

The README is blunt about the consequence and points to the AWS security best practices page for Amazon S3: unless you explicitly want everyone on the internet to read the bucket, your buckets should not be public. That warning is the operating condition for this whole tool, and it is the reason the tool is a good fit for public datasets and a bad fit for anything else.

## A source IP condition narrows the audience without changing the actions

The second policy in the README is the same grant with a condition attached, for the case where you want to restrict which networks can reach the bucket contents. It replaces the Sid with PrivateListGet and adds an IpAddress condition on aws:SourceIp, using the documentation CIDR block as the placeholder to be replaced with your own:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PrivateListGet",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "s3:List*",
        "s3:Get*"
      ],
      "Resource": [
        "arn:aws:s3:::BUCKET-NAME",
        "arn:aws:s3:::BUCKET-NAME/*"
      ],
      "Condition": {
          "IpAddress": {
              "aws:SourceIp": "203.0.113.0/24"
          }
      }
    }
  ]
}
```

The interesting part is what this does not change. The Principal is still *, so the policy still grants the actions to anyone, and the restriction is enforced by the condition rather than by narrowing the principal. Requests from addresses outside the CIDR are denied by the policy engine.

That has a practical consequence for anyone evaluating it: the restriction is at the network edge. It depends on the source address S3 sees, which is the client address for ordinary browser access, and it excludes anyone whose traffic arrives through a different path. It also means the bucket is still configured as publicly listable in the sense that the policy document contains a wildcard principal.

## The URL style you choose decides whether you need CORS

This is the detail in the README that most people trip over, and it is the part worth understanding before you deploy anything. There are two ways to reach the index.html file:

```bash
https://s3.amazonaws.com/BUCKET-NAME/index.html
https://BUCKET-NAME.s3.amazonaws.com/index.html
```

The first is path-style, where the bucket name is a path segment. The second is virtual-hosted-style, where the bucket name is in the host name.

If you use the virtual-hosted-style URL, the README says you should not need to enable CORS and you can skip that section entirely. It recommends this form. The reason is mechanical: with a path-style URL the page itself is served from s3.amazonaws.com, but the AWS JavaScript SDK then makes its requests to BUCKET-NAME.s3.amazonaws.com, and the browser blocks that because it is a different origin from the page. With a virtual-hosted-style URL the page and the API requests share a host, so no cross-origin allowance is required.

The README explains the underlying browser rule: browsers normally block requests from JavaScript to URLs unrelated to the source of the code, and CORS lets the bucket explicitly permit it. So the CORS configuration is not a security decision you are making on purpose, it is the price of the path-style URL.

## The CORS document is a short allowlist of origin, method and header

If you do need CORS, the README walks through the same console path, Permissions tab, then the CORS Configuration button, and gives a document to paste:

```json
[
  {
    "AllowedHeaders": [
      "*"
    ],
    "AllowedMethods": [
      "HEAD",
      "GET"
    ],
    "AllowedOrigins": [
      "https://s3.amazonaws.com"
    ],
    "ExposeHeaders": [
      "ETag",
      "x-amz-meta-custom-header"
    ]
  }
]
```

The methods are HEAD and GET only, which matches the read-only posture of the bucket policy. The origin is the path-style host. The exposed headers include ETag, which is what a browser needs in order to see object metadata in a cross-origin response.

The README is explicit about what this document is not. It does not authorize the user to perform any actions on the bucket, it simply enables the browser security model to allow the request to S3. Actual permissions still come from bucket permissions or IAM role level permissions.

One more case is covered. If your bucket is hosted outside the us-east-1 region and you want path-style URLs, the README says to use a region-specific endpoint such as https://s3-us-west-2.amazonaws.com/BUCKET_NAME/index.html, and to add the corresponding origins to AllowedOrigins, for example s3-us-west-2.amazonaws.com and s3.us-west-2.amazonaws.com. Each region endpoint added is another origin you are allowing, which is another entry to keep in sync with the buckets you operate.

## Static website hosting is the other half of the deployment

The README's static website hosting section is where the two URL forms are contrasted for serving the explorer as a site rather than as a file, so the details of the hosting options are best read from the project page.

The shape of it is already clear from the earlier sections. A bucket can serve its index.html as a website endpoint, which gives visitors a nicer URL than a raw object path, and then the visitor types the name of the bucket to explore. The explorer is the page, and the buckets it browses are separate from the bucket the page lives in, which is why one deployment can front many datasets.

That separation is also why the tool is safe to share as a link. The page itself carries no credentials, because it never makes an authenticated call. Everything it does is an anonymous list and get against a bucket that is already configured to be public. Anyone who loads the page has exactly the access any random visitor has, which is a simple and auditable security story.

It is also the limitation. There is no signed request path here, no session and no credential handling, so there is nothing to hook a private bucket into without turning the bucket public first.

## Where the S3 Explorer is the wrong tool

Private data is the obvious answer, and the README's own warning covers it. Every deployment of this explorer starts by making a bucket listable and readable by anyone, which means anyone who guesses or learns the bucket name can enumerate and download its contents. If the data is not intended to be public, this is the wrong tool and no amount of CORS configuration changes that.

The second wrong turn is using it as a general browser for many buckets without thinking about how the bucket name is discovered. The visitor types a bucket name into the page, and the page will try to list whatever it is given, subject to the bucket policy. That is convenient for a shared dataset and sloppy for a production endpoint.

The third limitation is the interface itself. The README describes a starting view, a folder view and a bucket view, and nothing more: no search, no download of selected objects in bulk, no upload, no authentication, no way to write. If you need to move data rather than look at it, the S3 APIs or the AWS command line are the tools for the job, and the explorer is only a viewer.

The fourth is that the project is a single file with a single release. The only tagged release listed is v1.0.0 from 2018-03-20, and the last push to the repository was on 2026-06-17, with the repository not archived. The release history is thin, so treat index.html as code you own and copy into your own bucket rather than as something with a support line behind it. The licence is Apache-2.0 with LICENSE and NOTICE.txt at the root of the repository; read those files for the terms, and this is not legal advice.

## Conclusion

aws-js-s3-explorer fits a team that publishes public datasets and wants a browsable listing without writing a server, and it costs one HTML file in a bucket plus a public read policy. It does not fit private data, and it does not fit anyone who cannot accept a bucket that anyone on the internet can list and read. Before deploying it, confirm that public listing is intended for that bucket, and serve the file through the virtual-hosted-style URL so you avoid the CORS configuration entirely.

## FAQ

### What does aws-js-s3-explorer do?

It is a JavaScript application that uses the AWS JavaScript SDK and the S3 APIs to make the contents of a public S3 bucket easy to browse in a web browser. The entire application lives in a single index.html file that you place in a bucket.

### Does aws-js-s3-explorer work with private S3 buckets?

No. The README says the v2-alpha branch that supported non-public buckets has been retired, and the current version browses public buckets, which is why the bucket policy grants s3:List* and s3:Get* to everyone.

### Do I need to configure CORS to use aws-js-s3-explorer?

Only if you reach the file with a path-style URL such as https://s3.amazonaws.com/BUCKET-NAME/index.html. The README recommends the virtual-hosted-style URL, which serves the page from the same host the SDK calls and needs no CORS configuration.

### How do I restrict access to the S3 bucket shown by the explorer?

The README gives a second bucket policy that adds an IpAddress condition on aws:SourceIp with your CIDR block, so only requests from those source addresses can list and get objects. The principal stays a wildcard and the restriction is enforced by the condition.

## Sources

- [awslabs/aws-js-s3-explorer on GitHub](https://github.com/awslabs/aws-js-s3-explorer)
- [Issues](https://github.com/awslabs/aws-js-s3-explorer/issues)
- [License: Apache-2.0](https://github.com/awslabs/aws-js-s3-explorer/blob/master/LICENSE)
- [README](https://github.com/awslabs/aws-js-s3-explorer/blob/master/README.md)
- [Releases](https://github.com/awslabs/aws-js-s3-explorer/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/awslabs-aws-js-s3-explorer
