A modular, extensible Python tool that audits a Salesforce org for security misconfigurations and generates a self-contained HTML report.
- 6 audit categories covering the most common Salesforce security risks
- Two modes: live org (SOQL via Salesforce CLI) and offline metadata XML parsing
- Single-file HTML report — severity scorecard, filterable findings, remediation guidance, print-friendly
- Plugin architecture — add a new audit category by dropping one file into
sf_audit/checkers/ - No external dependencies — Python 3.9+ stdlib only
- Standalone checkers — run any category in isolation, output as text or JSON
- Python 3.9+
- Salesforce CLI (
sf) — required for--orgmode only
git clone https://github.com/shinydrift/salesforce-security-audit.git
cd salesforce-security-audit
# No pip install needed — stdlib only# Authenticate with Salesforce CLI (one-time)
sf org login web --alias my-sandbox
# Run a full audit against a connected org
python sf_security_audit.py --org my-sandbox
# Run against previously retrieved metadata
python sf_security_audit.py --metadata-dir ./force-app
# Best coverage: live org data + metadata XML
python sf_security_audit.py --org my-sandbox --metadata-dir ./force-appThe report is saved to audit-report.html by default.
usage: sf_security_audit [-h] [--org ORG] [--metadata-dir DIR] [--output FILE]
[--categories KEYS] [--list-categories]
options:
--org, -o Salesforce org alias or username
--metadata-dir, -m Path to retrieved Salesforce metadata XML
--output, -r Output HTML report path (default: audit-report.html)
--categories, -c Comma-separated category keys to run (default: all)
--list-categories List available audit categories and exit
# Audit specific categories only
python sf_security_audit.py --org my-sandbox --categories user_permissions,api_login
# List all available categories
python sf_security_audit.py --list-categories
# Run a single checker in isolation — text output
python -m sf_audit.checkers.sharing --org my-sandbox
# Run a single checker — JSON output (pipe into jq, CI scripts, etc.)
python -m sf_audit.checkers.connected_apps --metadata-dir ./force-app --format json
# Extract metadata first, then audit offline
sf project retrieve start \
--metadata Profile,PermissionSet,SharingRules,ConnectedApp,SecuritySettings \
--output-dir ./metadata --target-org my-sandbox
python sf_security_audit.py --metadata-dir ./metadata --output report.html| Key | Category | Live Org | Metadata XML |
|---|---|---|---|
user_permissions |
User Permissions & Profiles | ✓ | ✓ |
sharing |
Sharing Rules & Org-Wide Defaults | ✓ | ✓ |
connected_apps |
Connected Apps & OAuth | ✓ | ✓ |
field_security |
Field-Level Security | ✓ | ✓ |
api_login |
API Access & Login History | ✓ | — |
password_session |
Password & Session Policies | ✓ | ✓ |
User Permissions & Profiles
- Profiles and custom permission sets with dangerous permissions:
ModifyAllData,ViewAllData,ManageUsers,ManageEncryptionKeys,AuthorApex, and 15 others - Active users with no login in ≥90 days (stale accounts)
- Permission set assignments granting elevated access
Sharing Rules & Org-Wide Defaults
- Objects with Public Read/Write internal OWD (any user can edit any record)
- Objects with Public Read/Write external sharing (exposes to guest users — critical)
- Sharing rules that grant access to all internal users
Connected Apps & OAuth
- Apps not restricted to admin-approved users
- OAuth scopes including
Full(complete API access) orRefreshToken(persistent offline access) - Missing IP range restrictions on OAuth callbacks
Field-Level Security
- Fields with sensitive names (
ssn,salary,password,credit_card,bank_account,dob,health, etc.) readable by many profiles or permission sets
API Access & Login History (live org only)
- Standard users with API access enabled
- Failed login attempts — 10+ per user flagged as possible brute force
- MFA enforcement coverage across active users
- Unusually high API call volumes (>5,000 calls/7 days)
Password & Session Policies
- Minimum password length < 10
- Weak complexity (
NoRestrictionorAlphaNumeric) - Non-expiring passwords or max age > 90 days
- Session timeout > 4 hours
- Sessions not locked on timeout
- Profiles without IP login restrictions
The generated report is a single self-contained HTML file with:
- Severity scorecard at the top (Critical / High / Medium / Low / Info counts)
- Filter buttons to show only findings of a given severity
- Collapsible sections per audit category
- Remediation guidance on every finding
- Print-friendly layout (no external CDN dependencies)
sf_audit/
base.py ← Finding dataclass + BaseChecker ABC with shared helpers
report.py ← HTML report generator
cli.py ← Auto-discovers checkers, runs them, writes report
checkers/
__init__.py ← pkgutil-based auto-discovery (discover())
user_permissions.py
sharing.py
connected_apps.py
field_security.py
api_login.py
password_session.py
sf_security_audit.py ← Entry point (3 lines)
tests/
test_checkers.py ← 28 unit tests
.claude/skills/ ← AgentOS skill definitions
- Create
sf_audit/checkers/<name>.py:
from sf_audit.base import BaseChecker, Finding
class MyChecker(BaseChecker):
category_key = "my_category" # used in --categories filter
category_name = "My Category" # shown in the report
category_emoji = "🔎"
order = 7 # controls report section order
def run(self) -> list[Finding]:
if self.org_alias:
# self._sf_query(soql) for live SOQL
# self._sf_query(soql, tooling=True) for Tooling API
pass
if self.metadata_dir:
# self._metadata_files("subdir", ".ext") + self._parse_xml(path)
pass
return self.findings-
(Optional) Create
.claude/skills/sf-audit-<name>.mdfor direct AgentOS invocation. -
Done —
discover()picks it up automatically. No changes to the orchestrator needed.
pip install pytest
pytest tests/ -v28 tests covering: Finding validation, BaseChecker helpers (deduplication, Salesforce ID format), all 6 XML-mode checkers, auto-discovery, and --categories validation.
- The tool is read-only — it never modifies org data
- In
--orgmode, it issues SOQL queries and Tooling API reads only - Credentials are managed entirely by the Salesforce CLI (
sf) — no secrets are stored by this tool - Salesforce IDs interpolated into SOQL are validated against the expected 15/18-character alphanumeric format before use
MIT
