{"id":15583509,"url":"https://github.com/osiegmar/s3-publisher-action","last_synced_at":"2026-03-07T07:01:43.706Z","repository":{"id":142852264,"uuid":"608697072","full_name":"osiegmar/s3-publisher-action","owner":"osiegmar","description":"GitHub Action to publish files to S3 with glob-based Cache-Control headers, modification detection, and multipart upload support","archived":false,"fork":false,"pushed_at":"2026-02-28T14:04:33.000Z","size":4253,"stargazers_count":4,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-28T19:44:58.986Z","etag":null,"topics":["actions","aws","aws-s3","deploy","deployment","github-actions","publish","publishing","s3"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osiegmar.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-03-02T14:56:13.000Z","updated_at":"2026-02-28T14:04:06.000Z","dependencies_parsed_at":"2023-08-27T05:15:26.204Z","dependency_job_id":"6ac72b86-255c-4f71-9d89-a42309cbdc61","html_url":"https://github.com/osiegmar/s3-publisher-action","commit_stats":null,"previous_names":["osiegmar/s3-publish-action"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/osiegmar/s3-publisher-action","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osiegmar%2Fs3-publisher-action","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osiegmar%2Fs3-publisher-action/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osiegmar%2Fs3-publisher-action/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osiegmar%2Fs3-publisher-action/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osiegmar","download_url":"https://codeload.github.com/osiegmar/s3-publisher-action/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osiegmar%2Fs3-publisher-action/sbom","scorecard":{"id":713522,"data":{"date":"2025-08-11","repo":{"name":"github.com/osiegmar/s3-publisher-action","commit":"3233b23844bbc98461b389fb246a791609db963d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/20 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/osiegmar/s3-publisher-action/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/osiegmar/s3-publisher-action/build.yml/main?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/build.yml:13","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":3,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/osiegmar/.github/SECURITY.md:1","Warn: no linked content found","Warn: One or no descriptive hints of disclosure, vulnerability, and/or timelines in security policy","Info: Found text in security policy: github.com/osiegmar/.github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":7,"reason":"3 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-22T08:49:19.976Z","repository_id":142852264,"created_at":"2025-08-22T08:49:19.976Z","updated_at":"2025-08-22T08:49:19.976Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30209411,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T05:23:27.321Z","status":"ssl_error","status_checked_at":"2026-03-07T05:00:17.256Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actions","aws","aws-s3","deploy","deployment","github-actions","publish","publishing","s3"],"created_at":"2024-10-02T20:08:47.802Z","updated_at":"2026-03-07T07:01:43.693Z","avatar_url":"https://github.com/osiegmar.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# S3 Publisher\n\n![Linter](https://github.com/osiegmar/s3-publisher-action/actions/workflows/linter.yml/badge.svg)\n![CI](https://github.com/osiegmar/s3-publisher-action/actions/workflows/ci.yml/badge.svg)\n![Check dist/](https://github.com/osiegmar/s3-publisher-action/actions/workflows/check-dist.yml/badge.svg)\n![CodeQL](https://github.com/osiegmar/s3-publisher-action/actions/workflows/codeql-analysis.yml/badge.svg)\n![Coverage](./badges/coverage.svg)\n\nUtility to publish files to a S3 bucket while maintaining file specific\nmetadata.\n\n## Why this action?\n\nWhen deploying a static site to S3, getting cache behavior right matters. Most\nS3 sync tools treat all files equally — they upload everything with the same (or\nno) `Cache-Control` header and don't consider file ordering. This often leads to\na common workaround: running a CloudFront invalidation after every deployment.\nBut CloudFront invalidation only clears the CDN cache — it cannot clear the\nbrowser cache of your users.\n\nA better approach starts with how modern sites are structured. Static site\ngenerators and build tools typically produce two kinds of files:\n\n- **Assets** (CSS, JS, images, fonts) with content hashes in their filenames,\n  like `style.a1b2c3.css`. These files never change — when the content changes,\n  the filename changes. They can safely be cached for a very long time with the\n  `immutable` directive.\n- **HTML files** that reference these assets. They should be cached for only a\n  short time (or not at all) so that visitors always get the latest version,\n  which in turn references the correct asset filenames.\n\nThis action is built around that idea. The `cache-control` option lets you\nassign different headers per glob pattern, and the `order` option controls which\nfiles are uploaded first. By uploading assets before HTML files, visitors never\nsee references to files that haven't been uploaded yet.\n\nWith this setup, CloudFront invalidation becomes unnecessary — HTML files expire\nfrom the cache within minutes, and hashed assets are immutable by design. For\nmore background on effective caching strategies, see\n[Caching Header Best Practices](https://simonhearne.com/2022/caching-header-best-practices/).\n\nWhile this action is optimized for static site publishing, it works for any S3\nupload scenario where file-specific metadata and upload ordering matter.\n\n## Quick start\n\nA very basic configuration for publishing files from a `public` directory to a\nbucket called `my-bucket-name`:\n\n```yaml\n- uses: osiegmar/s3-publisher-action@v2\n  with:\n    bucket: my-bucket-name\n    dir: public\n```\n\nSee [more examples](#examples).\n\n## Authentication\n\nThis action requires standard\n[AWS environment variables](https://docs.aws.amazon.com/sdkref/latest/guide/settings-reference.html#EVarSettings)\nset.\n\nMost common are `AWS_REGION`, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.\n\nYou can configure these environment variables either by using the\n[aws-actions/configure-aws-credentials](https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions)\naction (which is recommended) or by setting them manually.\n\n### Authentication via configure-aws-credentials-action\n\nThis example shows the use of\n[aws-actions/configure-aws-credentials](https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions)\nin order to authenticate against AWS.\n\n```yaml\non: [push]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    # These permissions are needed to interact with GitHub's OIDC Token endpoint.\n    permissions:\n      id-token: write\n      contents: read\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Configure AWS credentials\n        uses: aws-actions/configure-aws-credentials@v6\n        with:\n          role-to-assume: arn:aws:iam::XXXXXXXXXXXX:role/github-actions\n          aws-region: eu-central-1\n\n      - uses: osiegmar/s3-publisher-action@v2\n        with:\n          bucket: my-bucket-name\n          dir: ./public\n```\n\n### Manual authentication\n\nYou can also use GitHubs\n[Encrypted secrets](https://docs.github.com/de/actions/security-guides/encrypted-secrets)\nfeature to configure authentication manually. **This is not recommended!**\n\n```yaml\n- uses: osiegmar/s3-publisher-action@v2\n  with:\n    bucket: my-bucket-name\n    dir: ./public\n  env:\n    AWS_REGION: eu-central-1\n    AWS_ACCESS_KEY_ID: ${{ secrets.access_key_id }}\n    AWS_SECRET_ACCESS_KEY: ${{ secrets.secret_access_key }}\n```\n\n## Authorization\n\nRegardless of the way you configure the authentication you need to configure a\npolicy for granting the necessary permissions.\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:ListBucket\"],\n      \"Resource\": \"arn:aws:s3:::\u003cbucketname\u003e\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:PutObject\", \"s3:DeleteObject\", \"s3:AbortMultipartUpload\"],\n      \"Resource\": \"arn:aws:s3:::\u003cbucketname\u003e/*\"\n    }\n  ]\n}\n```\n\n\u003e The `s3:DeleteObject` permission is only required when the `delete-orphaned`\n\u003e option is enabled.\n\n## Configuration\n\n### Overview\n\n| Property           | Description                                   | required | default |\n| ------------------ | --------------------------------------------- | -------- | ------- |\n| bucket             | Destination S3 bucket to publish to           | true     |         |\n| dir                | Source directory read files from              | true     |         |\n| includes           | File globs to include                         | true     | \\*\\*/\\* |\n| excludes           | File globs to exclude                         | false    |         |\n| order              | File processing order globs                   | false    |         |\n| prefix             | S3 path prefix                                | false    |         |\n| cache-control      | Cache-Control configuration                   | false    |         |\n| force-upload       | Force publish (skip modification check)       | false    | false   |\n| delete-orphaned    | Delete remote files that do not exist locally | false    | false   |\n| wait-before-delete | Milliseconds to wait before deleting files    | false    |         |\n| dry-run            | Disable real changes                          | false    | false   |\n\n### Details\n\n**bucket**: The AWS S3 bucket to publish files to. The bucket needs to exist\nalready. Read\n[how to create a bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/creating-bucket.html).\n\n**dir**: The source directory where your files to publish resides in.\n\n**includes**: The file globs to include. By default, all files (`**/*`) are\nincluded. You can define multiple globs with this multiline input. Files that\nare both included **and** excluded are effectively **excluded**.\n\n**excludes**: The file globs to exclude. By default, no files are excluded. You\ncan define multiple globs with this multiline input. Files that are both\nincluded **and** excluded are effectively **excluded**.\n\n**order**: By default, files will be processed in arbitrary order. You can\ndefine multiple globs (comma separated) to specify a specific order. Everything\nthat does not match a glob will be processed in the end. Note that this action\nalways groups files in _new_ and _modified_ files while _new_ files are always\nprocessed first.\n\n**prefix**: A S3 path prefix can be configured optionally. A prefix `docs/`\ncould be configured for example in order to publish all files from the source\ndirectory into a 'docs-subdirectory' of your S3 bucket.\n\n**cache-control**: A Cache-Control header as defined by\n[RFC 9111](https://datatracker.ietf.org/doc/rfc9111/). Multiple globs and\nheaders can be defined with this multiline input. The first matching glob will\nbe used. If you change an already existing configuration, use `force-upload` to\nupdate existing files on S3. By default, no Cache-Control header will be set.\n\n**force-upload**: Force updating files regardless if their content has been\nchanged. This can be useful if you changed metadata (`cache-control`), as these\ndata can't be used for the size and hash based modification check.\n\n**delete-orphaned**: As a security measure, no files will be deleted by default.\nEnable this setting in order to delete files that exists on S3 but are removed\nfrom your local files. You can use `dry-run` to see what would happen then.\n\n**wait-before-delete**: Time in milliseconds to wait until deleting files from\nthe bucket. It can be beneficial to wait a few seconds before deleting orphaned\nfiles to let your users complete ongoing site loads (e.g. old HTML versions are\nalready loaded and these are referring to some old CSS/JS files). By default, no\nwait will happen.\n\n**dry-run**: Do not perform any real data modification. No files will be\nuploaded or deleted.\n\n## Examples\n\n**Include specific directories and exclude certain file types:**\n\n```yaml\n- uses: osiegmar/s3-publisher-action@v2\n  with:\n    bucket: my-bucket-name\n    dir: public\n    includes: |\n      assets/**/*\n      docs/**/*\n    excludes: |\n      **/*.zip\n      **/*.bak\n```\n\n**Full static site deployment — upload hashed assets first with long-lived cache\nheaders, use short cache times for HTML, and clean up orphaned files:**\n\n```yaml\n- uses: osiegmar/s3-publisher-action@v2\n  with:\n    bucket: my-bucket-name\n    dir: public\n    delete-orphaned: true\n    wait-before-delete: 3000\n    order: 'assets/**/*'\n    cache-control: |\n      assets/**/* = public, max-age=31536000, immutable\n      **/*.html = public, max-age=300\n      **/* = public, max-age=3600\n```\n\nThis example reflects the approach described in\n[Why this action?](#why-this-action). Hashed assets are uploaded first and\ncached for one year with the `immutable` directive. HTML files are cached for\nonly 5 minutes, so visitors quickly see the latest version pointing to the\ncorrect asset filenames. Everything else gets a moderate cache time. Orphaned\nfiles are cleaned up after a short delay to avoid breaking in-flight page loads.\nWith this setup, no CloudFront invalidation is needed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosiegmar%2Fs3-publisher-action","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosiegmar%2Fs3-publisher-action","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosiegmar%2Fs3-publisher-action/lists"}