saas-security-posture — Claude Code skill: SaaS stack audit
A skill that audits and hardens your company SaaS stack: SSO, OAuth grants, shadow IT and admin accounts across Slack, GitHub, Google Workspace and AWS.
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill saas-security-posture -a claude-codeWhat saas-security-posture does
The skill hands your agent ready-made commands per service. For Google Workspace it dumps user OAuth tokens with gam, for GitHub it lists installed apps and credential authorizations through gh api, for Slack it pulls approved and pending apps from the Admin API, for AWS it generates the IAM credential report. Output lands in CSV files plus a YAML inventory template with owner, sso and mfa fields.
Hardening comes next. On GitHub the skill turns on mandatory 2FA, checks SAML SSO identities, adds an IP allow list entry, applies branch protection to main with two approving reviews and no force pushes, and revokes stale PATs. On Slack it requires app approval, sets the workspace to invite-only, forces re-authentication every 24 hours and keeps messages for 365 days. In Google Workspace it enforces 2SV, a 14-character password minimum, blocks third-party OAuth with a client_id whitelist and stops external Drive sharing. In AWS it checks root MFA, creates a least-privilege permission set and service control policies.
You bring your own tokens and CLIs: SLACK_ADMIN_TOKEN, gam, gh, and the AWS CLI with IAM and SSO Admin rights. Without a Slack admin token the section 4 calls just return an auth error, so confirm your account has workspace admin rights before you start.
What it helps with
- build a SaaS inventory with owners and SSO status
- find OAuth apps holding broad scopes
- turn on mandatory 2FA for a GitHub org
- revoke stale personal access tokens
- shut down external Drive sharing and groups
Example prompts for the agent
- audit OAuth grants for every Google Workspace user and export to CSV
- require app approval in Slack and switch the workspace to invite-only
- list installed GitHub Apps in the org with their permissions
How to install saas-security-posture
1Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill saas-security-posture -a claude-code2Manually: put the files in place
~/.claude/skills/saas-security-posture/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/saas-security-posture/
1Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill saas-security-posture -a codex2Manually: put the files in place
.agents/skills/saas-security-posture/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill saas-security-posture -a cursor2Manually: put the files in place
.cursor/skills/saas-security-posture/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install BagelHole/DevOps-Security-Agent-Skills saas-security-posture2Manually: put the files in place
.github/skills/saas-security-posture/SKILL.md1Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill saas-security-posture -a gemini-cli2Platform CLI
gemini skills install https://github.com/BagelHole/DevOps-Security-Agent-Skills --path infrastructure/it/saas-security-postureManually: put the files in place
.devin/skills/saas-security-posture/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add BagelHole/DevOps-Security-Agent-Skills --skill saas-security-posture -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about saas-security-posture
How do I install saas-security-posture in Claude?
Run npx skills add BagelHole/DevOps-Security-Agent-Skills --skill saas-security-posture -a claude-code. The skill lands in ~/.claude/skills/saas-security-posture/SKILL.md.
What does saas-security-posture do?
It audits your SaaS stack and helps harden it: SSO and MFA, OAuth grants, shadow IT, and admin rights across Slack, GitHub, Google Workspace and AWS.
What do I need before running the skill?
Admin tokens and CLIs per service: SLACK_ADMIN_TOKEN, gam, gh, and the AWS CLI with IAM and SSO Admin access.
Source text
SKILL.md · MIT
---
name: saas-security-posture
description: Audit and harden your SaaS tool stack — enforce SSO, review OAuth grants, manage shadow IT, and secure admin accounts across Slack, GitHub, Google Workspace, and AWS. Use when tightening security across company SaaS tools.
license: MIT
metadata:
author: devops-skills
version: "1.0"
---
# SaaS Security Posture Management for Startups
Secure every SaaS tool your company relies on with practical, command-driven hardening.
## 1. When to Use This Skill
- **SOC 2 preparation** — auditors need evidence of MFA, access controls, and OAuth governance.
- **Suspicious OAuth app** — an employee authorized a third-party app with broad scopes.
- **SaaS sprawl** — teams sign up for tools with company email and nobody tracks them.
- **Post-incident hardening** — after phishing or credential leaks, tighten every surface.
## 2. SaaS Inventory Audit
### Google Workspace — OAuth Grants
```bash
gam all users show tokens > oauth_tokens_audit.csv
```
### GitHub — Installed Apps
```bash
gh api /orgs/{ORG}/installations --paginate \
--jq '.installations[] | {app: .app_slug, permissions: .permissions, created: .created_at}'
gh api /orgs/{ORG}/credential-authorizations --paginate \
--jq '.[] | {login: .login, credential_type: .credential_type}'
```
### Slack — Approved and Pending Apps
```bash
curl -s -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
"https://slack.com/api/admin.apps.approved.list" | jq '.approved_apps[] | {name: .app.name, id: .app.id}'
curl -s -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
"https://slack.com/api/admin.apps.requests.list" | jq '.app_requests[]'
```
### AWS — IAM Credential Report
```bash
aws iam generate-credential-report
aws iam get-credential-report --output text --query 'Content' | base64 -d > iam_credential_report.csv
```
### Master Inventory Template
```yaml
tools:
- name: Google Workspace
owner: [email protected]
sso: true
mfa: enforced
- name: GitHub Enterprise
owner: [email protected]
sso: true
mfa: enforced
- name: Slack Business+
owner: [email protected]
sso: true
app_approval: required
- name: AWS Organizations
owner: [email protected]
sso: true
scp_enforced: true
```
---
## 3. GitHub Security Hardening
```bash
# Enforce 2FA and find non-compliant members
gh api -X PATCH /orgs/{ORG} -f two_factor_requirement_enabled=true
gh api /orgs/{ORG}/members?filter=2fa_disabled --paginate --jq '.[].login'
# Verify SAML SSO identities
gh api /orgs/{ORG}/credential-authorizations --paginate \
--jq '.[] | {login: .login, saml_name_id: .saml_name_id}'
# Add IP allow list entry
gh api -X POST /orgs/{ORG}/ip-allow-list \
-f allow_list_value="203.0.113.0/24" -f name="Office VPN" -F is_active=true
# Branch protection on main
gh api -X PUT /repos/{ORG}/{REPO}/branches/main/protection \
-H "Accept: application/vnd.github+json" --input - <<'EOF'
{
"required_status_checks": {"strict": true, "contexts": ["ci/build","ci/test"]},
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 2,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true
},
"restrictions": null,
"allow_force_pushes": false,
"allow_deletions": false
}
EOF
# Audit PATs and revoke stale tokens
gh api /orgs/{ORG}/personal-access-tokens --paginate \
--jq '.[] | {owner: .owner.login, name: .token_name, expires: .token_expires_at}'
gh api -X DELETE /orgs/{ORG}/personal-access-tokens/{PAT_ID}
# Audit deploy keys and webhooks
for repo in $(gh repo list {ORG} --limit 500 --json name -q '.[].name'); do
gh api /repos/{ORG}/${repo}/keys --jq '.[] | {title: .title, read_only: .read_only}'
done
gh api /orgs/{ORG}/hooks --jq '.[] | {url: .config.url, events: .events, active: .active}'
```
---
## 4. Slack Security
```bash
# Require app approval
curl -s -X POST -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
"https://slack.com/api/admin.apps.config.set" -d '{"app_approval_enabled": true}'
# Set workspace to invite-only
curl -s -X POST -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
"https://slack.com/api/admin.teams.settings.setDiscoverability" \
-d '{"team_id": "T0XXXXXXX", "discoverability": "invite_only"}'
# Force re-authentication every 24 hours
curl -s -X POST -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
"https://slack.com/api/admin.teams.settings.setSessionDuration" \
-d '{"team_id": "T0XXXXXXX", "session_duration": 86400}'
# Set message retention to 1 year
curl -s -X POST -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
"https://slack.com/api/admin.teams.settings.setRetentionPolicy" \
-d '{"team_id": "T0XXXXXXX", "retention_type": "all", "retention_duration": 365}'
# Audit Slack Connect shared channels
curl -s -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
"https://slack.com/api/admin.conversations.search?search_channel_types=connect" \
| jq '.conversations[] | {name: .name, is_ext_shared: .is_ext_shared}'
```
---
## 5. Google Workspace Hardening
```bash
# Enforce 2-Step Verification and strong passwords
gam update org "/" settings 2sv enforced
gam update org "/" settings password_length 14
# Block all third-party OAuth apps, then whitelist specific ones
gam update org "/" settings oauth_access block_all
gam update org "/" settings oauth_access whitelist client_id:APP_CLIENT_ID_1
# Disable external Drive sharing and file transfers
gam update org "/" settings drive sharing_outside_domain off
gam update org "/" settings drive transfer_to_personal off
gam update org "/" settings groups external_members off
# Verify email authentication records
dig TXT company.com | grep "v=spf1"
dig TXT google._domainkey.company.com
dig TXT _dmarc.company.com
# Expected: v=DMARC1; p=reject; rua=mailto:[email protected]; pct=100
# Mobile device management
gam update org "/" settings mobile management advanced
gam update org "/" settings mobile screen_lock required
gam update org "/" settings mobile encryption required
gam update mobile ${DEVICE_ID} action wipe # compromised device
```
---
## 6. AWS Account Security
```bash
# Root account lockdown — verify MFA, remove access keys
aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'
aws iam get-account-summary --query 'SummaryMap.AccountAccessKeysPresent'
# SSO permission set with least privilege
aws sso-admin create-permission-set --instance-arn "${SSO_INSTANCE_ARN}" \
--name "DeveloperAccess" --session-duration "PT8H"
aws sso-admin attach-managed-policy-to-permission-set \
--instance-arn "${SSO_INSTANCE_ARN}" --permission-set-arn "${PERMISSION_SET_ARN}" \
--managed-policy-arn "arn:aws:iam::aws:policy/ReadOnlyAccess"
```
### Service Control Policies
```json
{
"Version": "2012-10-17",
"Statement": [
{"Sid": "DenyRootActions", "Effect": "Deny", "Action": "*", "Resource": "*",
"Condition": {"StringLike": {"aws:PrincipalArn": "arn:aws:iam::*:root"}}},
{"Sid": "DenyLeaveOrg", "Effect": "Deny",
"Action": "organizations:LeaveOrganization", "Resource": "*"}
]
}
```
```bash
aws organizations create-policy --name "DenyRootActions" \
--type SERVICE_CONTROL_POLICY --content file://deny-root-actions.json
aws organizations attach-policy --policy-id "${POLICY_ID}" --target-id "${ORG_ROOT_ID}"
# Organization-wide CloudTrail
aws cloudtrail create-trail --name org-security-trail \
--s3-bucket-name company-cloudtrail-logs \
--is-multi-region-trail --is-organization-trail --enable-log-file-validation
aws cloudtrail start-logging --name org-security-trail
```
---
## 7. OAuth App Review
### Identify High-Risk Grants
```bash
# Google — find apps with dangerous scopes
gam all users show tokens | grep -E "(drive|gmail|admin)" > high_risk_oauth.txt
# GitHub — find apps with write access
gh api /orgs/{ORG}/installations --paginate \
--jq '.installations[] | select(.permissions.contents == "write") | {app: .app_slug}'
```
### Revoke Dangerous Grants
```bash
gam user [email protected] delete token clientid APP_CLIENT_ID # single app
gam user [email protected] delete tokens # all apps
gh api -X DELETE /orgs/{ORG}/installations/{INSTALLATION_ID} # GitHub app
curl -s -X POST -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
"https://slack.com/api/admin.apps.uninstall" -d '{"app_id": "A0XXXXXXX"}'
```
### Scope Risk Classification
```
CRITICAL — revoke unless justified:
Google: mail.google.com, admin.directory.user | GitHub: admin:org, repo | Slack: admin
HIGH — review carefully:
Google: googleapis.com/auth/drive | GitHub: contents:write | Slack: channels:read
LOW — generally safe:
Google: userinfo.email | GitHub: read:org | Slack: identity.basic
```
---
## 8. Admin Account Protection
```bash
# Dedicated admin account in Google Workspace
gam create user [email protected] firstname "John (Admin)" lastname "Doe" \
password "$(openssl rand -base64 32)" org "/Admins"
gam update user [email protected] admin on
# Require hardware security keys for the Admins OU
gam update org "/Admins" settings 2sv security_key_only
# AWS MFA enforcement policy
cat <<'EOF' > enforce-mfa-policy.json
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyUnlessMFA", "Effect": "Deny",
"NotAction": ["iam:CreateVirtualMFADevice","iam:EnableMFADevice",
"iam:GetUser","iam:ListMFADevices","sts:GetSessionToken"],
"Resource": "*",
"Condition": {"BoolIfExists": {"aws:MultiFactorAuthPresent": "false"}}
}]
}
EOF
aws iam create-policy --policy-name EnforceMFA --policy-document file://enforce-mfa-policy.json
# Break-glass account for SSO outages
BREAK_GLASS_PW=$(openssl rand -base64 48)
gam create user [email protected] firstname "Break" lastname "Glass" \
password "${BREAK_GLASS_PW}" org "/Admins" admin on
# Store password in a sealed envelope in a physical safe
# After every use: rotate password, re-seal, log the incident
```
---
## 9. Data Loss Prevention
```bash
# Google Drive — block external sharing and restrict viewers
gam update org "/" settings drive sharing_outside_domain off
gam update org "/" settings drive disable_download_print_copy_for_viewers on
# GitHub — enable secret scanning and push protection org-wide
gh api -X PATCH /orgs/{ORG} -f security_product=secret_scanning -f enablement=enable_all
gh api -X PATCH /orgs/{ORG} -f security_product=secret_scanning_push_protection -f enablement=enable_all
gh api /orgs/{ORG}/secret-scanning/alerts --paginate \
--jq '.[] | {repo: .repository.name, secret_type: .secret_type, state: .state}'
# Slack — restrict data export to org admins
curl -s -X POST -H "Authorization: Bearer ${SLACK_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
"https://slack.com/api/admin.teams.settings.setExportRestrictions" \
-d '{"team_id": "T0XXXXXXX", "export_type": "org_admins_only"}'
# AWS — block all public S3 access at account level
aws s3control put-public-access-block --account-id "${AWS_ACCOUNT_ID}" \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
```
---
## 10. Shadow IT Detection
### DNS-Based Discovery
```bash
SHADOW_IT_DOMAINS=("airtable.com" "notion.so" "trello.com" "asana.com"
"monday.com" "clickup.com" "figma.com" "canva.com" "miro.com"
"zapier.com" "dropbox.com" "box.com" "wetransfer.com")
for domain in "${SHADOW_IT_DOMAINS[@]}"; do
count=$(grep -c "${domain}" /var/log/dns/query.log 2>/dev/null || echo "0")
[ "${count}" -gt 0 ] && echo "DETECTED: ${domain} — ${count} queries"
done
```
### Google Workspace Login Audit
```bash
gam report login parameters app_name \
start_time "2026-03-01T00:00:00Z" end_time "2026-03-24T23:59:59Z" > login_audit.csv
gam report token > token_usage_report.csv
```
### Proxy Log Analysis
```bash
awk '{print $7}' /var/log/squid/access.log | sed 's|https\?://||;s|/.*||' \
| sort | uniq -c | sort -rn | head -50 > top_domains.txt
comm -23 <(awk '{print $2}' top_domains.txt | sort) \
<(yq '.tools[].domains[]' saas-inventory.yaml | sort) > unapproved.txt
```
### Automated Alerting
```bash
cat <<'SCRIPT' > /usr/local/bin/shadow-it-check.sh
#!/usr/bin/env bash
set -euo pipefail
APPROVED="/etc/security/approved-saas-domains.txt"
YESTERDAY=$(date -d "yesterday" +%d-%b-%Y)
grep "${YESTERDAY}" /var/log/dns/query.log | awk '{print $4}' | sort -u > /tmp/today.txt
NEW=$(comm -23 /tmp/today.txt <(sort "${APPROVED}"))
[ -n "${NEW}" ] && mail -s "[ALERT] Shadow IT" [email protected] <<< "${NEW}"
SCRIPT
chmod +x /usr/local/bin/shadow-it-check.sh
echo "0 8 * * * root /usr/local/bin/shadow-it-check.sh" >> /etc/cron.d/shadow-it-check
```
---
## Quick Reference — Top 10 Priority Actions
| # | Action | Scope |
|---|--------|-------|
| 1 | Enforce MFA/2FA everywhere | Google, GitHub, AWS, Slack |
| 2 | Enable SSO with your IdP | All tools |
| 3 | Audit and revoke OAuth grants | Google, GitHub |
| 4 | Require Slack app approval | Slack |
| 5 | Branch protection on main | GitHub |
| 6 | Secret scanning + push protection | GitHub |
| 7 | Block public S3 buckets | AWS |
| 8 | Enable org-wide CloudTrail | AWS |
| 9 | Disable external Drive sharing | Google |
| 10 | Create break-glass admin accounts | Google, AWS |
## Maintenance Cadence
**Weekly:** Review OAuth grants, secret scanning alerts, Slack app queue.
**Monthly:** AWS IAM report, rotate service keys, admin account review, shadow IT scan.
**Quarterly:** Full SaaS inventory refresh, OAuth pruning, break-glass test, SCP updates.
© devops-skills · MIT · BagelHole/DevOps-Security-Agent-Skills