least-privilege-access

verified

37bfe6d9-c2a6-4de0-8645-8945594d1b5e

Scope credentials, tokens, and permissions to the minimum needed — per-service keys, scoped roles, no shared admin. Use when setting up access for apps, CI, or agents.

Metadata

Skill ID
37bfe6d9-c2a6-4de0-8645-8945594d1b5e
Version
1
Owner
387274b7-2891-478b-81b8-e11d5adb9319
Tags
least-privilegepermissionsiamscopingtokensaccess-control
Signature
verified
Integrity
OK
Content hash
e4907686910e403f77d3c4bb8cd3501499f58ef07c37babd2ea5a227ae3f6d0b
Created
2026-08-15T05:24:22Z

Skill file

Raw skill file (markdown source)
# Least Privilege Access

Use when setting up access for services, CI pipelines, agents, or human users — apply the principle that every actor should have only the permissions they need, and nothing more.

## The Core Checklist

1. **One identity per consumer**: Separate API keys/tokens for CI, local dev, production services, and each team member.
2. **Read-only by default**: Start with read-only; only add write permissions when a specific operation requires it.
3. **Scoped to the resource, not the account**: Grant access to `repo-xyz` not "all repos"; `bucket-x` not "all buckets".
4. **Short expiry + rotation**: Tokens expire in hours/days, not years. Automate rotation where possible.
5. **No human credentials for bots**: CI/agents get their own service accounts, never a team member's credentials.

## Scoping by Environment

| Environment | Access Pattern | Example |
|---|---|---|
| Production service | Read/write to its own resources only | DB user `app_prod` with CRUD on `app_db` only |
| CI pipeline | Read source + write artifacts + deploy | GitHub Actions: `contents: read`, `deployments: write` |
| Local development | Read-only to dev/staging resources | DB user `dev_readonly` with SELECT only |
| Agent/automation | Minimal scope, short-lived token | GitHub fine-grained PAT: single repo, 7-day expiry |
| Admin/on-call | Broader read access, write only via break-glass | Separate "admin" role, not the default |

## Concrete Examples

### GitHub Fine-Grained PAT (for CI)
```bash
# Create via gh CLI (or web UI)
# Repository access: ONLY the specific repo
# Permissions: Contents: Read, Pull requests: Write
# Expiration: 30 days (or 7 for sensitive repos)
```

### AWS IAM Policy (for a service)
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::my-specific-bucket",
        "arn:aws:s3:::my-specific-bucket/*"
      ]
    }
  ]
}
```
Note: This allows read-only on ONE bucket. No `s3:*`, no `*` resource.

### Database User (PostgreSQL)
```sql
-- Create a read-only user for a specific database
CREATE USER report_reader WITH PASSWORD '...';
GRANT CONNECT ON DATABASE myapp TO report_reader;
GRANT USAGE ON SCHEMA public TO report_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO report_reader;

-- NOT: GRANT ALL PRIVILEGES ON DATABASE myapp TO report_reader;
```

### Docker / Container
```dockerfile
# Run as non-root user
FROM python:3.11-slim
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
COPY --chown=appuser:appuser . /app
```

## Auditing Existing Access

```bash
# GitHub: list all repo collaborators and their permissions
gh api repos/:owner/:repo/collaborators --jq '.[] | "\(.login): \(.permissions)"'

# List your own tokens
gh auth token list  # GitHub CLI

# AWS: find overly permissive policies
aws iam list-policies --scope Local --only-attached \
  --query 'Policies[?contains(PolicyName, `Admin`)]'

# Find users with direct admin access (no group)
aws iam list-users --query 'Users[].UserName' --output text | while read user; do
  policies=$(aws iam list-attached-user-policies --user-name "$user" \
    --query 'AttachedPolicies[].PolicyName' --output text)
  [ -n "$policies" ] && echo "$user: $policies"
done
```

## Role Separation Table

| Role | Can Read | Can Write | Can Deploy | Can Manage IAM |
|---|---|---|---|---|
| Developer | Yes (dev/staging) | Dev/staging only | No | No |
| CI Bot | Source only | Artifacts | Yes (automated) | No |
| Production Service | Its own DB | Its own DB only | No | No |
| On-Call | Yes (all) | Emergency only | Yes | No |
| Admin | Yes (all) | Yes | Yes | Yes |

## Guardrails

- **Never** share credentials between services or people — every actor gets its own identity.
- **Never** use a root/admin account for daily operations.
- **Never** grant broad scopes "to avoid future work" — future work should earn its access.
- **Never** hardcode tokens in config files that get checked in.
- **Always** review IAM/permissions during onboarding of new services and offboarding of old ones.

## Pitfalls

- **One admin token shared everywhere "for convenience"**: If one consumer is compromised, all are.
- **Granting `*` permissions because you're unsure what's needed**: Audit logs will show exactly what's used; scope down from there.
- **Long-lived tokens that never rotate**: A token that never expires is a permanent backdoor.
- **Human credentials in CI/CD**: When a team member leaves, their credentials are revoked — and CI breaks.
- **Read-only as an afterthought**: Default to read-only; write is the exception that requires justification.

## Verify / Checklist

- [ ] Every service/consumer has its own distinct credential (no sharing)
- [ ] All tokens have an expiry date (preferably ≤90 days)
- [ ] No human credentials used in CI/CD or automation
- [ ] Production services have write access ONLY to their own resources
- [ ] Audit logs confirm no unexpected broad access patterns
- [ ] Offboarding checklist includes revoking all credentials for departing members/services
- [ ] Break-glass admin access exists but is separate from daily-use credentials

Attached files

No attached files.