prowler-cloud/prowler/prowler-api

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Prowler API patterns: RLS, RBAC, providers, Celery tasks. Trigger: When working in api/ on models/serializers/viewsets/filters/tasks involving tenant isolation (RLS), RBAC, or provider lifecycle.

Install

npx vskill@latest install prowler-cloud/prowler/prowler-api

SKILL.md

When to Use

Use this skill for Prowler-specific patterns:

  • Row-Level Security (RLS) / tenant isolation
  • RBAC permissions and role checks
  • Provider lifecycle and validation
  • Celery tasks with tenant context
  • Multi-database architecture (4-database setup)

For generic DRF patterns (ViewSets, Serializers, Filters, JSON:API), use django-drf skill.


Critical Rules

  • ALWAYS use rls_transaction(tenant_id) when querying outside ViewSet context
  • ALWAYS use get_role() before checking permissions (returns FIRST role only)
  • ALWAYS use @set_tenant then @handle_provider_deletion decorator order
  • ALWAYS use explicit through models for M2M relationships (required for RLS)
  • NEVER access Provider.objects without RLS context in Celery tasks
  • NEVER bypass RLS by using raw SQL or connection.cursor()
  • NEVER use Django's default M2M - RLS requires through models with tenant_id
Note: rls_transaction() accepts both UUID objects and strings - it converts internally via str(value).

Architecture Overview

4-Database Architecture
DatabaseAliasPurposeRLS
defaultprowler_userStandard API queriesYes
adminadminMigrations, auth bypassNo
replicaprowler_userRead-only queriesYes
admin_replicaadminAdmin read replicaNo
python
# When to use admin (bypasses RLS)
from api.db_router import MainRouter
User.objects.using(MainRouter.admin_db).get(id=user_id) # Auth lookups
# Standard queries use default (RLS enforced)
Provider.objects.filter(connected=True) # Requires rls_transaction context
RLS Transaction Flow
text
Request → Authentication → BaseRLSViewSet.initial()
│
├─ Extract tenant_id from JWT
├─ SET api.tenant_id = 'uuid' (PostgreSQL)
└─ All queries now tenant-scoped

Implementation Checklist

When implementing Prowler-specific API features:

#PatternReferenceKey Points
1RLS Modelsapi/rls.pyInherit RowLevelSecurityProtectedModel, add constraint
2RLS Transactionsapi/db_utils.pyUse rls_transaction(tenant_id) context manager
3RBAC Permissionsapi/rbac/permissions.pyget_role(), get_providers(), Permissions enum
4Provider Validationapi/models.pyvalidate_<provider>_uid() methods on Provider model
5Celery Taskstasks/tasks.py, api/decorators.py, config/celery.pyTask definitions, decorators (@set_tenant, @handle_provider_deletion), RLSTask base
6RLS Serializersapi/v1/serializers.pyInherit RLSSerializer to auto-inject tenant_id
7Through Modelsapi/models.pyALL M2M must use explicit through with tenant_id
Full file paths: See references/file-locations.md

Decision Trees

Which Base Model?
text
Tenant-scoped data → RowLevelSecurityProtectedModel
Global/shared data → models.Model + BaseSecurityConstraint (rare)
Partitioned time-series → PostgresPartitionedModel + RowLevelSecurityProtectedModel
Soft-deletable → Add is_deleted + ActiveProviderManager
Which Manager?
text
Normal queries → Model.objects (excludes deleted)
Include deleted records → Model.all_objects
Celery task context → Must use rls_transaction() first
Which Database?
text
Standard API queries → default (automatic via ViewSet)
Read-only operations → replica (automatic for GET in BaseRLSViewSet)
Auth/admin operations → MainRouter.admin_db
Cross-tenant lookups → MainRouter.admin_db (use sparingly!)
Celery Task Decorator Order?
python
@shared_task(base=RLSTask, name="...", queue="...")
@set_tenant # First: sets tenant context
@handle_provider_deletion # Second: handles deleted providers
def my_task(tenant_id, provider_id):
pass

RLS Model Pattern

python
from api.rls import RowLevelSecurityProtectedModel, RowLevelSecurityConstraint
class MyModel(RowLevelSecurityProtectedModel):
# tenant FK inherited from parent
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
name = models.CharField(max_length=255)
inserted_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)
class Meta(RowLevelSecurityProtectedModel.Meta):
db_table = "my_models"
constraints = [
RowLevelSecurityConstraint(
field="tenant_id",
name="rls_on_%(class)s",
statements=["SELECT", "INSERT", "UPDATE", "DELETE"],
),
]
class JSONAPIMeta:
resource_name = "my-models"
M2M Relationships (MUST use through models)
python
class Resource(RowLevelSecurityProtectedModel):
tags = models.ManyToManyField(
ResourceTag,
through="ResourceTagMapping", # REQUIRED for RLS
)
class ResourceTagMapping(RowLevelSecurityProtectedModel):
# Through model MUST have tenant_id for RLS
resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
tag = models.ForeignKey(ResourceTag, on_delete=models.CASCADE)
class Meta:
constraints = [
RowLevelSecurityConstraint(
field="tenant_id",
name="rls_on_%(class)s",
statements=["SELECT", "INSERT", "UPDATE", "DELETE"],
),
]

Async Task Response Pattern (202 Accepted)

For long-running operations, return 202 with task reference:

python
@action(detail=True, methods=["post"], url_name="connection")
def connection(self, request, pk=None):
with transaction.atomic():
task = check_provider_connection_task.delay(
provider_id=pk, tenant_id=self.request.tenant_id
)
prowler_task = Task.objects.get(id=task.id)
serializer = TaskSerializer(prowler_task)
return Response(
data=serializer.data,
status=status.HTTP_202_ACCEPTED,
headers={"Content-Location": reverse("task-detail", kwargs={"pk": prowler_task.id})}
)

Providers (11 Supported)

ProviderUID FormatExample
AWS12 digits123456789012
AzureUUID v4a1b2c3d4-e5f6-...
GCP6-30 chars, lowercase, letter startmy-gcp-project
M365Valid domaincontoso.onmicrosoft.com
Kubernetes2-251 charsarn:aws:eks:...
GitHub1-39 charsmy-org
IaCGit URLhttps://github.com/user/repo.git
Oracle CloudOCID formatocid1.tenancy.oc1..
MongoDB Atlas24-char hex507f1f77bcf86cd799439011
Alibaba Cloud16 digits1234567890123456

Adding new provider: Add to ProviderChoices enum + create validate_<provider>_uid() staticmethod.


RBAC Permissions

PermissionControls
MANAGE_USERSUser CRUD, role assignments
MANAGE_ACCOUNTTenant settings
MANAGE_BILLINGBilling/subscription
MANAGE_PROVIDERSProvider CRUD
MANAGE_INTEGRATIONSIntegration config
MANAGE_SCANSScan execution
UNLIMITED_VISIBILITYSee all providers (bypasses provider_groups)
RBAC Visibility Pattern
python
def get_queryset(self):
user_role = get_role(self.request.user)
if user_role.unlimited_visibility:
return Model.objects.filter(tenant_id=self.request.tenant_id)
else:
# Filter by provider_groups assigned to role
return Model.objects.filter(provider__in=get_providers(user_role))

Celery Queues

QueuePurpose
scansProwler scan execution
overviewDashboard aggregations (severity, attack surface)
complianceCompliance report generation
integrationsExternal integrations (Jira, S3, Security Hub)
deletionProvider/tenant deletion (async)
backfillHistorical data backfill operations
scan-reportsOutput generation (CSV, JSON, HTML, PDF)

Task Composition (Canvas)

Use Celery's Canvas primitives for complex workflows:

PrimitiveUse For
chain()Sequential execution: A → B → C
group()Parallel execution: A, B, C simultaneously
CombinedChain with nested groups for complex workflows
Note: Use .si() (signature immutable) to prevent result passing. Use .s() if you need to pass results.
Examples: See assets/celery_patterns.py for chain, group, and combined patterns.

Beat Scheduling (Periodic Tasks)

OperationKey Points
Create scheduleIntervalSchedule.objects.get_or_create(every=24, period=HOURS)
Create periodic taskUse task name (not function), kwargs=json.dumps(...)
Delete scheduled taskPeriodicTask.objects.filter(name=...).delete()
Avoid race conditionsUse countdown=5 to wait for DB commit
Examples: See assets/celery_patterns.py for schedule_provider_scan pattern.

Advanced Task Patterns

@set_tenant Behavior
Modetenant_id in kwargstenant_id passed to function
@set_tenant (default)Popped (removed)NO - function doesn't receive it
@set_tenant(keep_tenant=True)Read but keptYES - function receives it
Key Patterns
PatternDescription
bind=TrueAccess self.request.id, self.request.retries
get_task_logger(__name__)Proper logging in Celery tasks
SoftTimeLimitExceededCatch to save progress before hard kill
countdown=30Defer execution by N seconds
eta=datetime(...)Execute at specific time
Examples: See assets/celery_patterns.py for all advanced patterns.

Celery Configuration

SettingValuePurpose
BROKER_VISIBILITY_TIMEOUT86400 (24h)Prevent re-queue for long tasks
CELERY_RESULT_BACKENDdjango-dbStore results in PostgreSQL
CELERY_TASK_TRACK_STARTEDTrueTrack when tasks start
soft_time_limitTask-specificRaises SoftTimeLimitExceeded
time_limitTask-specificHard kill (SIGKILL)
Full config: See assets/celery_patterns.py and actual files at config/celery.py, config/settings/celery.py.

UUIDv7 for Partitioned Tables

Finding and ResourceFindingMapping use UUIDv7 for time-based partitioning:

python
from uuid6 import uuid7
from api.uuid_utils import uuid7_start, uuid7_end, datetime_to_uuid7
# Partition-aware filtering
start = uuid7_start(datetime_to_uuid7(date_from))
end = uuid7_end(datetime_to_uuid7(date_to), settings.FINDINGS_TABLE_PARTITION_MONTHS)
queryset.filter(id__gte=start, id__lt=end)

Why UUIDv7? Time-ordered UUIDs enable PostgreSQL to prune partitions during range queries.


Batch Operations with RLS

python
from api.db_utils import batch_delete, create_objects_in_batches, update_objects_in_batches
# Delete in batches (RLS-aware)
batch_delete(tenant_id, queryset, batch_size=1000)
# Bulk create with RLS
create_objects_in_batches(tenant_id, Finding, objects, batch_size=500)
# Bulk update with RLS
update_objects_in_batches(tenant_id, Finding, objects, fields=["status"], batch_size=500)

Security Patterns

Full examples: See assets/security_patterns.py
Tenant Isolation Summary

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
13,380
Repository forks
2,049
7-day trend score
48

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 22 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/prowler-cloud/prowler/prowler-api/badge)](https://verified-skill.com/skills/prowler-cloud/prowler/prowler-api)

Something wrong with this skill or its page? Tell us.