tigris-security-access-control
Use when configuring CORS, rotating access keys, setting bucket policies, or securing Tigris storage — covers key lifecycle, roles, CORS rules, presigned URL security, audit checklist
Savant verdict: Pending analysis
Imported from the source hub; Savant's analysis is still running.
Live evaluation
Not evaluated yet. Workspaces can request a live evaluation.
Safety (NVIDIA SkillSpector)
Scan pending.
Structure
- No license declaredConfirm you may reuse this skill before importing it into your repository.
SKILL.md
---
name: tigris-security-access-control
description: Use when configuring CORS, rotating access keys, setting bucket policies, or securing Tigris storage — covers key lifecycle, roles, CORS rules, presigned URL security, audit checklist
---
# Tigris Security & Access Control
Configure access keys, CORS rules, bucket visibility, and presigned URL security for Tigris object storage. Covers key lifecycle management, role-based access, and security auditing.
## Prerequisites
**Before doing anything else**, install the Tigris CLI if it's not already available:
```bash
tigris help || npm install -g @tigrisdata/cli
```
If you need to install it, tell the user: "I'm installing the Tigris CLI (`@tigrisdata/cli`) so we can work with Tigris object storage."
## Quick Reference
| Operation | Command |
|-----------|---------|
| Create key | `tigris access-keys create "my-key"` |
| List keys | `tigris access-keys list` |
| Assign to bucket | `tigris access-keys assign <tid> --bucket <name> --role Editor` |
| Revoke key | `tigris access-keys delete <tid>` |
| Set CORS | `tigris buckets cors set <bucket> --config cors.json` |
| Get CORS | `tigris buckets cors get <bucket>` |
| Remove CORS | `tigris buckets cors delete <bucket>` |
---
## Access Key Lifecycle
### Create
```bash
tigris access-keys create "production-app-key"
# Output:
# Access Key ID: tid_xxx
# Secret Access Key: tsec_yyy ← shown ONLY once, save immediately
```
### Assign to Bucket
```bash
# Editor: read + write + delete
tigris access-keys assign tid_xxx --bucket my-app-uploads --role Editor
# ReadOnly: read only
tigris access-keys assign tid_xxx --bucket my-app-uploads --role ReadOnly
```
| Role | Read | Write | Delete | Use When |
|------|------|-------|--------|----------|
| `Editor` | Yes | Yes | Yes | App servers that upload/modify files |
| `ReadOnly` | Yes | No | No | Services that only read/serve files |
### Scope Keys Narrowly
Create separate keys for different concerns:
```bash
# Key for the app server (read/write to uploads bucket)
tigris access-keys create "app-server"
tigris access-keys assign tid_app --bucket app-uploads --role Editor
# Key for the CDN/read service (read-only)
tigris access-keys create "cdn-reader"
tigris access-keys assign tid_cdn --bucket app-uploads --role ReadOnly
# Key for backups (write to backup bucket only)
tigris access-keys create "backup-writer"
tigris access-keys assign tid_bak --bucket app-backups --role Editor
```
### Rotate Keys (Zero Downtime)
```bash
# 1. Create new key
tigris access-keys create "production-app-key-v2"
tigris access-keys assign tid_new --bucket my-app-uploads --role Editor
# 2. Update application environment with new key
# (deploy with new TIGRIS_STORAGE_ACCESS_KEY_ID / SECRET)
# 3. Verify app works with new key
# 4. Revoke old key
tigris access-keys delete tid_old
```
### List and Audit
```bash
# List all access keys
tigris access-keys list
# Check which buckets a key can access
tigris access-keys info tid_xxx
```
---
## CORS Configuration
CORS is required for browser-based uploads (direct uploads, presigned PUT URLs).
### Set CORS Rules
```bash
tigris buckets cors set my-app-uploads --config cors.json
```
### Development (Localhost)
```json
{
"CORSRules": [
{
"AllowedOrigins": ["http://localhost:3000", "http://localhost:5173"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "Content-Length"],
"MaxAgeSeconds": 3600
}
]
}
```
### Production (Specific Domain)
```json
{
"CORSRules": [
{
"AllowedOrigins": ["https://myapp.com", "https://www.myapp.com"],
"AllowedMethods": ["GET", "PUT", "HEAD"],
"AllowedHeaders": ["Content-Type", "Content-MD5", "Content-Disposition"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 86400
}
]
}
```
### Combined Dev + Production
```json
{
"CORSRules": [
{
"AllowedOrigins": [
"https://myapp.com",
"https://www.myapp.com",
"http://localhost:3000"
],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "Content-Length"],
"MaxAgeSeconds": 3600
}
]
}
```
### Permissive (Any Origin)
```json
{
"CORSRules": [
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 86400
}
]
}
```
**Warning:** Only use `"*"` origins for truly public, read-only content. Never allow `PUT`/`DELETE` from any origin.
### Verify CORS
```bash
# Check current CORS config
tigris buckets cors get my-app-uploads
# Test with curl
curl -I -H "Origin: https://myapp.com" \
-H "Access-Control-Request-Method: PUT" \
-X OPTIONS \
https://my-app-uploads.t3.storage.dev/test
```
---
## Public vs Private Buckets
| Setting | Who Can Read | URL Access | Use For |
|---------|-------------|------------|---------|
| Private (default) | Only authenticated requests | Presigned URLs | User documents, sensitive files |
| Public | Anyone with URL | Direct URL | Static assets, public images, CDN content |
```bash
# Create public bucket
tigris buckets create my-public-assets --public
# Create private bucket (default)
tigris buckets create my-private-docs
```
---
## Presigned URL Security
### Best Practices
| Parameter | Recommendation |
|-----------|---------------|
| Expiration (download) | 5-60 minutes for most use cases |
| Expiration (upload) | 5-15 minutes |
| Scope | One URL per file, one operation per URL |
```typescript
import { getPresignedUrl } from "@tigrisdata/storage";
// Short-lived download URL
const { data } = await getPresignedUrl("documents/report.pdf", {
operation: "get",
expiresIn: 300, // 5 minutes
});
// Short-lived upload URL with content type restriction
const { data } = await getPresignedUrl("uploads/photo.jpg", {
operation: "put",
expiresIn: 600,
contentType: "image/jpeg", // Client must upload this type
});
```
### Never Do
- Set expiration longer than 24 hours
- Generate presigned URLs for entire bucket prefixes
- Share presigned URLs in public channels (they're secrets)
- Use presigned URLs as permanent links (use public bucket URLs instead)
---
## Environment Variable Security
```bash
# .env (never commit this file)
TIGRIS_STORAGE_ACCESS_KEY_ID=tid_xxx
TIGRIS_STORAGE_SECRET_ACCESS_KEY=tsec_yyy
```
```bash
# .gitignore (must include)
.env
.env.local
.env.*.local
```
For CI/CD, use your platform's secrets management:
```bash
# GitHub Actions
gh secret set TIGRIS_STORAGE_ACCESS_KEY_ID
gh secret set TIGRIS_STORAGE_SECRET_ACCESS_KEY
# Vercel
vercel env add TIGRIS_STORAGE_ACCESS_KEY_ID
# Fly.io
fly secrets set TIGRIS_STORAGE_ACCESS_KEY_ID=tid_xxx
```
---
## Security Audit Checklist
- [ ] **No keys in code** — search for `tid_` and `tsec_` in your repo
- [ ] **`.env` in `.gitignore`** — credentials never committed
- [ ] **Keys scoped to buckets** — no unassigned keys with global access
- [ ] **Roles match need** — read-only services use `ReadOnly` role
- [ ] **CORS not overly permissive** — no `"*"` origin with write methods
- [ ] **Presigned URLs have short expiry** — under 1 hour for most cases
- [ ] **Unused keys revoked** — no stale keys from former team members or old deployments
- [ ] **Separate keys per environment** — dev/staging/production use different keys
---
## Key Compromise Response
If an access key is exposed (committed to git, leaked in logs, etc.):
```bash
# 1. Immediately revoke the compromised key
tigris access-keys delete tid_compromised
# 2. Create a new key
tigris access-keys create "replacement-key"
tigris access-keys assign tid_new --bucket my-bucket --role Editor
# 3. Update all deployments with the new key
# (update .env, CI/CD secrets, deployment configs)
# 4. Audit access — check for unauthorized uploads/downloads
tigris ls t3://my-bucket --recursive -l | sort -k4 -r | head -50
# 5. If key was committed to git, rotate and consider the entire
# git history compromised — use git-filter-repo to remove it
```
---
## Critical Rules
**Always:** Create separate keys per environment and concern | Assign keys to specific buckets with minimum required role | Rotate keys periodically (quarterly recommended) | Set CORS to specific origins in production | Use short-lived presigned URLs
**Never:** Commit keys to git | Use a single key for all environments | Set CORS to `"*"` with write methods | Share presigned URLs publicly | Keep unused keys active
---
## Known Issues
| Problem | Fix |
|---------|-----|
| CORS preflight fails | Check `AllowedOrigins` includes your exact origin (with protocol) |
| "Access denied" after key rotation | Ensure new key is assigned to bucket with correct role |
| Secret key lost | Cannot recover — create new key and reassign |
| Browser upload fails silently | Check CORS config includes `PUT` in `AllowedMethods` |
---
## Related Skills
- **file-storage** — CLI setup and access key creation
- **tigris-s3-migration** — CORS and credential setup during migration
## Official Documentation
- Tigris: https://www.tigrisdata.com/docs/