LeastPriv
LeastPriv reads your JavaScript and TypeScript source code, finds every cloud, database, payment, auth, and messaging call it makes across thirteen providers, and maps each call to the permission it requires. It can compare two git branches to show exactly what permissions a pull request adds or removes, and it can gate your CI pipeline on the risky ones.
What is LeastPriv
Most permission scanning tools look at your infrastructure
configuration, things like Terraform, CloudFormation, or IAM policy
JSON. LeastPriv looks at your application code instead. It
parses your source directly and detects the actual SDK calls your code
makes at runtime, things like an S3Client deleting an
object, a Supabase client removing a user, or a Firestore document
being updated.
The output is a permission tree for each file you scan, and a permission diff between two branches. That same diff is what runs automatically on a pull request through GitHub Actions.
Why static analysis, not an LLM
LeastPriv is built on deterministic AST parsing using
ts-morph, rather than an LLM reading the code and
guessing. A security gate needs to produce the same result on the
same input every time, and it needs to be auditable, meaning you can
point at the exact line and node that triggered a finding. A
probabilistic tool can miss things silently or give a different
answer on different runs, and that rules it out for something that
decides whether a pull request can merge.
Architecture
Parser
ts-morph wraps the TypeScript compiler, which gives symbol aware traversal instead of raw text matching.
Provenance tracking
Tracks which variable came from which SDK or service, so cache.delete() is never confused with db.collection().delete().
Rules tables
Per-provider JSON mapping each SDK method to a permission string and a risk category. AWS rules are generated automatically from installed SDK packages.
Git diffing
Uses git worktree to materialize two branches into isolated temp directories without touching your working tree.
AWS
AWS coverage is generated from the SDK packages themselves. Every
@aws-sdk/client-* package exports its full list of
Command classes, and LeastPriv reads that list directly instead of
relying on a hand maintained list.
| Service | IAM prefix |
|---|---|
| S3 | s3: |
| DynamoDB | dynamodb: |
| Lambda | lambda: |
| SQS | sqs: |
| SNS | sns: |
| IAM | iam: |
| Secrets Manager | secretsmanager: |
| CloudWatch Logs | logs: |
| API Gateway | apigateway: |
| SES | ses: |
| Cognito Identity Provider | cognito-idp: |
| KMS | kms: |
| STS | sts: |
| EventBridge | events: |
| SSM | ssm: |
| Kinesis | kinesis: |
| Step Functions | states: |
A few services are intentionally left out: EC2, RDS, ALB, Route 53, CloudFront, and ECS. These are provisioning and infrastructure services, not services your application code calls at runtime.
new DeleteObjectCommand(...)). v2
covers the older namespaced style, using patterns like
const AWS = require("aws-sdk"),
new AWS.S3(), and s3.deleteObject(...) with
no Command classes at all. v2 is officially in maintenance mode but
is still common in older production code, which is why both are
supported.
Firebase
Full surface coverage across both SDK generations:
- v8 and Admin SDK: chained calls such as
db.collection("users").doc(id).delete() - v9 modular SDK: a functional style such as
deleteDoc(doc(db, "users", id)), including renamed destructured imports - Firestore, Storage, Auth, Realtime Database, Cloud Functions, Cloud Messaging, and Remote Config
- Transactions (
runTransactioncallback binding) and batched writes
Detection relies on variable provenance tracking, a symbol table
built during parsing that knows db came from
admin.firestore(). Because of that, a plain
Map or an unrelated object with a .delete()
method is never mistaken for a real Firebase call.
Supabase
- Postgrest (tables):
select,insert,update,upsert,delete,rpc - Storage:
upload,download,remove,list,move,copy,createSignedUrl - Auth Admin:
createUser,deleteUser,updateUserById,listUsers - Edge Functions:
functions.invoke() - Realtime:
channel(),subscribe(),removeChannel()
Stripe
Covers the shared method pattern used across every Stripe resource
(retrieve, list, create,
update, del, cancel), plus
specific overrides for methods whose risk depends on which resource
they're called on.
| Method | Category |
|---|---|
refunds.create | delete |
customers.del | delete |
subscriptions.cancel | delete |
payouts.create | admin |
paymentIntents.cancel | delete |
webhookEndpoints.create | admin |
refunds.create, payouts.create, and subscriptions.cancel move real money or stop active billing, so they're treated as the highest risk operations in this provider.
Auth0
Covers users, roles, clients, connections, organizations, Actions, attack protection settings, and tickets.
- Users:
create,update,delete, RBAC role and permission assignment, MFA enrollment management - Roles & Clients: creation, updates, deletion, and secret rotation
- Organizations: membership, invitations, and role assignment within a tenant
- Actions: the serverless code that runs inside Auth0's login pipeline, including
deploy - Attack protection: brute force and suspicious IP throttling configuration
assignRoles, assignPermissions, and any Action deploy are categorized as admin, since they change what a user or a live login flow is authorized to do.
Clerk
Covers users, organizations, sessions, invitations, allowlist and blocklist identifiers, JWT templates, and redirect URLs.
- Users:
createUser,updateUser,banUser,lockUser,deleteUser - Organizations: membership and role changes, invitations, deletion
- Sessions: token generation and revocation
- Allowlist / blocklist: which identifiers are permitted or blocked from signing up
banUser and deleteUser are categorized as admin and delete respectively, since one revokes active access immediately and the other removes the account entirely.
Twilio
Covers messages, calls, verification (2FA/OTP), services, API keys, and regulatory addresses.
- Messages & calls: sending, listing, and redacting SMS, voice, and WhatsApp communication
- Verifications: dispatching and checking OTP codes
- API keys: creation and revocation
keys.create is flagged as a high risk admin operation, since a leaked or over-broadly scoped API key can send messages or place calls on the account's behalf.
Resend
Covers emails, domains, API keys, audiences, contacts, and batch sending.
- Emails:
send, status lookups, canceling a scheduled send - Domains: registering and verifying a sending domain
- Audiences & contacts: managing marketing subscriber lists
apiKeys.create is flagged as high risk, since a new key can send email on behalf of the domain the moment it's created.
Pinecone
Covers vector data operations and index level control.
- Vector data:
query,upsert,fetch,deleteOne,deleteMany,deleteAll - Index control: creation, configuration, and deletion of indexes and backup collections
deleteAll and deleteIndex are flagged as the highest severity operations in this provider, since both are irreversible and can wipe an entire namespace or index of embeddings used for retrieval.
Prisma
Covers standard model CRUD plus Prisma's raw SQL escape hatches.
- Reads:
findUnique,findFirst,findMany,count,aggregate,groupBy - Writes:
create,createMany,update,updateMany,upsert - Deletes:
delete,deleteMany - Raw SQL:
$queryRaw,$queryRawUnsafe,$executeRaw,$executeRawUnsafe,$transaction
The raw SQL methods are categorized as admin regardless of what the underlying query does, since a static analyzer can't always see inside a raw SQL string to know if it's a read or a write.
Drizzle ORM
Covers the query builder's core methods: select, selectDistinct, insert, update, delete, execute, and transaction, along with the SQLite specific result methods run, all, get, and values.
execute is categorized as admin, since it runs a raw SQL statement or prepared query directly.
Mongoose
Covers model level operations built on top of the native MongoDB driver.
- Reads:
find,findOne,findById,aggregate,countDocuments - Writes:
create,insertMany,updateOne,updateMany,save - Deletes:
deleteOne,deleteMany,findByIdAndDelete - Schema/index management:
syncIndexes,createIndexes,cleanIndexes
MongoDB (native driver)
Covers the collection, database, and client levels of the official driver.
- Collection: the usual CRUD, plus index management and
drop - Database:
createCollection,dropCollection,dropDatabase, user management - Client: connection lifecycle and session handling
dropDatabase is the single highest risk operation covered anywhere in LeastPriv's rule sets. It's categorized as delete and destroys every collection in the database at once.
Raw SQL
Statement level detection across the SQL keywords used by Postgres,
MySQL, and SQLite, covering everything from SELECT and
INSERT through administrative statements like
GRANT, VACUUM, and FLUSH.
| Category | Examples |
|---|---|
| read | SELECT, WITH, SHOW, EXPLAIN |
| write | INSERT, UPDATE, UPSERT, MERGE, CALL |
| delete | DELETE, TRUNCATE, DROP |
| admin | GRANT, REVOKE, VACUUM, FLUSH, KILL |
Installation
npm install -g LeastPriv
Or run it without installing anything:
npx LeastPriv analyze ./src
LeastPriv analyze
Scans a single file or an entire directory and prints a permission tree.
LeastPriv analyze avatar.js
LeastPriv analyze ./src
Example output:
📄 avatar.js HIGH RISK
════════════════════
└── 🔶 AWS
└── s3 (1 operations)
└── DeleteObjectCommand DELETE (line 7)
├── 📦 bucketName
├── 🔑 s3:DeleteObject
└── 📝 Permanently deletes a file from S3 storage
📊 Summary
═══════════════════════════════════════════
Total Operations: 1
By Category:
DELETE 1
⚠️ Risk Assessment: High-risk operations detected
Directories are scanned recursively. node_modules, .git, and dist are skipped.
LeastPriv diff
Compares permissions between two git branches. This is the core command behind the pull request check.
LeastPriv diff main feature
LeastPriv diff main feature --repo ./path/to/repo
LeastPriv diff main feature --block
Example output:
📊 Permission diff: main → feature
+ Added (1):
+ aws:s3.DeleteObjectCommand (delete) — app.js:4
⚠️ WARNING: This PR adds 1 sensitive permission(s). Please review carefully:
⚠️ aws:s3.DeleteObjectCommand (delete) — app.js:4
This is a warning only — merge is not blocked.
| Flag | Effect |
|---|---|
--repo <path> | Path to the git repository (defaults to the current directory) |
--json | Outputs the diff as JSON instead of formatted text |
--block | Exits with code 1 on sensitive additions (the default is to warn only, exiting 0) |
JSON output
Both commands accept --json for machine readable output. This is what the GitHub Action uses internally, and it works just as well for any other tooling you want to build on top.
{
"provider": "aws",
"service": "s3",
"operation": "DeleteObjectCommand",
"category": "delete",
"cloudPermission": "s3:DeleteObject",
"description": "Permanently deletes a file from S3 storage",
"resource": "bucketName",
"location": { "file": "avatar.js", "line": 7, "column": 19 }
}
GitHub Actions integration
Add a workflow that runs on every pull request. It checks out both
the pull request branch and LeastPriv's own source, then runs
diff between main and the incoming branch.
name: LeastPriv Permission Check
on:
pull_request:
branches: [main]
jobs:
permission-check:
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
fetch-depth: 0
path: target-repo
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install LeastPriv
run: npm install -g LeastPriv
- name: Run permission diff
run: LeastPriv diff origin/main HEAD --repo ./target-repo
GitHub reads the exit code of the last step and shows a pass or fail status directly on the pull request. If you also add a branch protection rule requiring this check, a failing result can block the merge button until someone reviews it.
Blocking vs. warning
By default, diff always exits 0. Sensitive permissions
are printed as a clear warning, but the check still passes and
nothing is blocked. This is the recommended default for most teams,
because a tool that blocks by default tends to get bypassed or
disabled the first time it produces a false positive.
| Mode | Command | Result on sensitive change |
|---|---|---|
| Warn (default) | LeastPriv diff main feature | ⚠️ Warning shown, exit 0, check passes |
| Block | LeastPriv diff main feature --block | ❌ Warning shown, exit 1, check fails |
--block once you have
confirmed the false positive rate on your actual codebase is low
enough that a blocked merge is always meaningful.
Detection model
Every finding is a structured object with the same shape, regardless of provider:
| Field | Meaning |
|---|---|
provider | aws, firebase, or supabase |
service | for example s3, firestore, or postgrest |
operation | The SDK method or command name that was detected |
category | read write delete admin auth |
cloudPermission | The formal permission string, where one exists. This only applies to AWS, since Firebase and Supabase don't have an equivalent formal permission model. |
resource | The table, bucket, or document path, when it can be extracted from the call |
location | The file, line, and column of the detected call |
category is the one field with a consistent meaning
across all three providers. AWS is the only provider with a formal,
published permission naming system, so Firebase and Supabase
operations are categorized using LeastPriv's own taxonomy instead.
Limitations
- It only sees SDK calls made directly in application code. Infrastructure defined in Terraform, CloudFormation, or the AWS, GCP, or Azure console is invisible to it, by design.
- Cross file variable provenance, such as a client initialized in one file and used in another, is not yet tracked for every provider. This currently works reliably within a single file.
- GCP and Azure are not covered.
- Redis and other in-memory cache access (
ioredis,redis) is not yet covered.
Roadmap
- Redis and ioredis detection, including flagging destructive commands like
FLUSHALL - Cross file provenance tracking across all providers, so a client initialized in one file is still recognized when used in another
- TypeORM and Sequelize, to round out ORM coverage alongside Prisma and Drizzle
- GCP and Azure providers
- Posting warnings as a PR comment through the GitHub API, so they're visible without opening the Action logs
Contact
Feel free to reach out with questions, feedback, or ideas.
- Email: abdubey405@gmail.com
- LinkedIn: linkedin.com/in/abhinav-dubey-0532a6375
- GitHub: AbhinavDubey4056