audit-tenant
/octopus:audit-tenant scans the current branch (or any ref) for missing
tenant-scope enforcement before merge. It produces a severity-tiered report
with confidence labels across six checks specific to multi-tenant SaaS
codebases.
What it solves
In a multi-tenant application, the worst bugs are the ones that work correctly
for most users β and silently leak data to others. A query using
IgnoreQueryFilters() without justification, a new entity added to the
DbContext without HasQueryFilter, a controller that finds by id from the
route without an ownership check β each of these is a potential data-leak path
that passes unit tests and passes code review unless someone is specifically
looking for it. audit-tenant looks for it on every merge, automatically.
How it works
The command resolves the diff, identifies tenant-relevant files using keyword heuristics, and runs six checks:
- T1 query-without-filter β flags
IgnoreQueryFilters()calls without a// tenant-override: <reason>comment. Severity: block. - T2 dbcontext-missing-filter β flags new
DbSet<X>properties in the configuredDbContextthat have noHasQueryFilterorIEntityTypeConfiguration<X>in the same diff. Severity: block. - T3 raw-sql-no-filter β flags
FromSqlRaw,ExecuteSqlRaw, and similar helpers where the SQL string does not contain the configured tenant field. Severity: block. - T4 id-from-route-no-ownership β warns when a controller method looks up a
tenant-scoped entity by route
idwithout calling an ownership helper. Severity: warn (defense-in-depth β the query filter already enforces scope, but a futureIgnoreQueryFilters()would expose this path). - T5 join-to-unfiltered-table β warns when a LINQ joinβs right-side
DbSetis not tenant-scoped and the join predicate does not restrict by the tenant field. Severity: warn. - T6 cross-tenant-admin-endpoint β warns when an
[Authorize(Roles = "Admin")]or[AllowAnonymous]method accesses a tenant-scopedDbSetwithout an// across-tenants: <reason>comment. Severity: warn.
Tenant-scope config (field, filter, context, entities) is resolved from
.octopus.ymlβs tenantScope: block, falling back to safe defaults.
Usage & parameters
/octopus:audit-tenant [ref] [--base=main] [--only=<checks>] [--write-report]refβ PR number (#123), URL, branch name, or commit SHA. Default: currentHEADvs its upstream.--base=<branch>β base branch for the diff. Default:main.--only=<list>β comma-separated subset ofquery-without-filter,dbcontext-missing-filter,raw-sql-no-filter,id-from-route-no-ownership,join-to-unfiltered-table,cross-tenant-admin-endpoint.--write-reportβ persists the report todocs/reviews/YYYY-MM-DD-tenant-<slug>.md.