Live bucket monitor in a single HTML file. Polls ListObjectsV2, diffs snapshots, and keeps the full event history in a persistent DuckDB database (browser OPFS) — close the tab, come back, keep tracking. Timeline, SQL console, and Parquet/CSV export included.
Memory-only, never stored. Use short-lived credentials — never a root key.
./aws-credentials.json (same origin — see setup below). Nothing to paste.Needs only s3:ListBucket — works for non-owners with read access. Settings (not secrets) are remembered in localStorage.
Browsers cannot call EC2 instance metadata (169.254.169.254) directly — it sends no CORS headers. Instead, refresh a same-origin JSON file server-side; the page picks it up with zero keys pasted:
# run on the EC2 instance (cron every 30 min — creds rotate, refresh early)
TOKEN=$(curl -s -X PUT http://169.254.169.254/latest/api/token -H 'X-aws-ec2-metadata-token-ttl-seconds: 3600')
ROLE=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/)
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps({'AccessKeyId':d['AccessKeyId'],'SecretAccessKey':d['SecretAccessKey'],'Token':d['Token']}))" \
> /var/www/html/aws-credentials.json
# serve this HTML from the same directory: python3 -m http.server 8000 -d /var/www/html
# then open http://<instance-ip>:8000/s3watch-static-draft.html
Same-origin fetch = no CORS involved. The instance role needs s3:ListBucket on the target bucket(s). The JSON file is only reachable inside your VPC.
The page calls the S3 API cross-origin from your browser, so the bucket must allow it. S3 console → bucket → Permissions → CORS:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["http://<your-ec2-ip>:8000"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
Use the exact origin serving this file (* works for testing but exposes LIST to every website). Without this the page cannot reach S3 — no static workaround exists.
ListObjectsV2 (paginated) → JS diff vs last snapshot → new/changed/deleted rows appended to a persistent DuckDB table events in OPFS (opfs://s3watch.duckdb). History survives tab close and browser restart; reopening resumes tracking from the stored state.file:// or an old browser falls back to in-memory (history lost on reload) — the chip in the header shows which mode you're in. One tab at a time can hold the database file.ceil(objects/1000) LIST requests (Class B). The request counter keeps it visible.| time | event | key | size |
|---|
Table events(key VARCHAR, event_type VARCHAR, size BIGINT, etag VARCHAR, event_time TIMESTAMPTZ, cycle INTEGER). Event types: CREATED, MODIFIED, DELETED.