An OpenClaw skill for secure file sharing via Amazon S3 with time-limited pre-signed URLs.
This skill teaches OpenClaw how to:
- Upload files to S3 and generate shareable download links
- Create pre-signed URLs for existing S3 objects
- Generate upload pages for receiving files from others
- Manage secure file sharing without exposing S3 buckets publicly
Perfect for sharing logs, screenshots, packages, or any files with automatic expiration.
β
Upload & Share - Upload files and get instant download links
β
Clean Filenames - Downloads have user-friendly names (no timestamps)
β
Collision-Free - Timestamp prefixes prevent overwriting
β
Auto-Expiration - Links expire after 24 hours (configurable)
β
Upload Pages - Create web pages for others to send you files
β
No Public Bucket - All access via secure pre-signed URLs
β
Rate Limited - Built-in protection against abuse
- AWS Account with S3 access
- IAM Permissions:
s3:PutObjects3:GetObject
- Node.js >= 18.0.0
- S3 Bucket (can be private)
- OpenClaw installed on an EC2 instance in AWS cloud
cd ~/.openclaw/workspace/skills
git clone https://github.com/aws-samples/sample-OpenClaw-on-AWS-with-Bedrock.git
cp -r sample-OpenClaw-on-AWS-with-Bedrock/skills/s3-files-skill/skills/s3-files ./
cd s3-files
npm installCopy the example config:
cp config.example.json config.jsonEdit config.json with your settings:
{
"bucketName": "your-s3-bucket-name",
"region": "us-west-2",
"defaultExpirationHours": 24,
"maxUploadSizeMB": 100
}Create an IAM policy with these permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}Attach to your IAM user or role.
aws configure
# Enter your AWS Access Key ID
# Enter your AWS Secret Access Key
# Enter your default region (e.g., us-west-2)Or use environment variables:
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_REGION="us-west-2"cd ~/.openclaw/workspace/skills/s3-files
node upload.js /path/to/report.pdfOutput:
π€ Uploading report.pdf...
β
Upload complete!
π Generating download URL...
π S3 Key: uploads/1772120357022-report.pdf
π₯ Download as: report.pdf
β
Download URL:
https://your-bucket.s3.us-west-2.amazonaws.com/uploads/1772120357022-report.pdf?...
π¦ File: uploads/1772120357022-report.pdf (245.67 KB)
Share the download URL with anyone. They'll download it as report.pdf (clean filename, no timestamp).
node download-url.js uploads/1772120357022-report.pdf 48
# Generate 48-hour link for existing fileGenerate a web page for someone to upload files to you:
node generate-upload-page.js 50
# Max 50MB file sizeOutput:
π€ Generating upload page...
β
Upload page created!
π Page URL (24h expiration):
https://your-bucket.s3.us-west-2.amazonaws.com/upload-page-1772120400000.html?...
π¦ Files will be uploaded to S3 with key: upload-1772120400000
Share the page URL. When someone uploads, you'll get the file in S3 as upload-1772120400000.
The Problem:
Without prefixes, uploading report.pdf twice overwrites the first file.
The Solution:
- Storage: Files stored with timestamp prefix (
uploads/1772120357022-report.pdf) - Download: Browser saves as clean filename (
report.pdf) - How: Uses S3's
Content-Dispositionheader
Result:
β
No collisions (timestamp ensures uniqueness)
β
Clean downloads (users see friendly names)
β
Sortable by time (timestamp in S3 key)
All file access uses AWS pre-signed URLs:
- No public bucket needed - Bucket stays private
- Automatic expiration - Links expire after set time (default 24h)
- No credential sharing - URLs include temporary credentials
- Secure access - Only people with the link can download
# Upload log file
node upload.js /var/log/application.log
# Send download URL to support team
# URL expires in 24 hours automatically# Upload APK/ZIP/installer
node upload.js ~/Downloads/app-v2.0.apk
# Share link with testers
# Clean filename: app-v2.0.apk (no timestamp)# Generate upload page
node generate-upload-page.js 100
# Share page URL with colleague
# They upload file via browser
# You download it later# Upload screenshot
node upload.js ~/Desktop/screenshot.png
# Quick share link (expires in 24h)ββββββββββββββββ
β β
β OpenClaw β
β β
ββββββββ¬ββββββββ
β
β upload.js / download-url.js
β
βΌ
ββββββββββββββββ ββββββββββββββββββ
β β β β
β AWS S3 ββββββββ€ Pre-signed β
β Bucket β β URLs β
β β β (24h expire) β
ββββββββ¬ββββββββ ββββββββββββββββββ
β
β Download
β
βΌ
ββββββββββββββββ
β β
β End User β
β Browser β
β β
ββββββββββββββββ
Flow:
- OpenClaw uploads file to S3 (with timestamp prefix)
- Generates pre-signed URL (with clean filename header)
- User clicks link β downloads as clean filename
- URL expires automatically after 24 hours
# Block public access (recommended)
aws s3api put-public-access-block \
--bucket your-bucket-name \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# Enable versioning (optional, protects against accidental overwrites)
aws s3api put-bucket-versioning \
--bucket your-bucket-name \
--versioning-configuration Status=EnabledAuto-delete old files to save costs:
{
"Rules": [
{
"Id": "Delete old uploads",
"Status": "Enabled",
"Prefix": "uploads/",
"Expiration": {
"Days": 7
}
}
]
}Apply with:
aws s3api put-bucket-lifecycle-configuration \
--bucket your-bucket-name \
--lifecycle-configuration file://lifecycle.json- Use least-privilege permissions (only PutObject, GetObject)
- Scope to specific bucket (
arn:aws:s3:::bucket-name/*) - Don't use root credentials (create IAM user/role)
- Rotate access keys regularly
- Use IAM roles for EC2/Lambda instead of keys
S3 Pricing (us-west-2, approximate):
| Item | Price | Example Usage | Cost |
|---|---|---|---|
| Storage | $0.023/GB/month | 10GB | $0.23 |
| PUT requests | $0.005/1000 | 100 uploads | $0.001 |
| GET requests | $0.0004/1000 | 1000 downloads | $0.0004 |
| Data transfer | First 100GB free | 50GB out | $0 |
Typical monthly cost: < $1 for moderate use (100 uploads, 1000 downloads, 10GB storage)
Prices vary by region. See AWS S3 Pricing for details.
Cause: Missing IAM permissions
Fix: Add s3:PutObject and s3:GetObject to your IAM policy
Cause: Bucket name incorrect or wrong region
Fix: Check config.json bucket name and region match your S3 bucket
Cause: Too many requests in short time
Fix: Wait 60 seconds. Built-in rate limiter allows 10 requests/minute
Cause: Pre-signed credentials expired (1 hour validity)
Fix: Regenerate upload page with node generate-upload-page.js
Cause: File exceeds maxUploadSizeMB setting
Fix: Increase in config.json or split file into chunks
# Upload with 48-hour link
node upload.js file.pdf
node download-url.js uploads/1234-file.pdf 48
# Upload page with 2-hour validity
# (Edit generate-upload-page.js: Expires: 7200)# Upload with custom key structure
node upload.js file.pdf my-prefix/custom-name.pdfconst { uploadFile } = require('./upload.js');
const { generateDownloadUrl } = require('./download-url.js');
const { generateUploadPage } = require('./generate-upload-page.js');
// Upload file
const result = await uploadFile('/path/to/file.pdf');
console.log('Download URL:', result.downloadUrl);
// Generate URL for existing file
const url = await generateDownloadUrl('uploads/file.pdf', 48);
// Create upload page
const page = await generateUploadPage(100);
console.log('Upload page:', page.pageUrl);s3-files/
βββ upload.js # Upload files to S3
βββ download-url.js # Generate pre-signed download URLs
βββ generate-upload-page.js # Create upload pages
βββ config.example.json # Configuration template
βββ package.json # Node.js dependencies
βββ SKILL.md # Skill metadata for OpenClaw
Found a bug or have a feature request?
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT-0 License - see the LICENSE file for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- OpenClaw Discord: Join Community
Made with β€οΈ for the OpenClaw community