Community plugin security model¶
This reference explains who can do what in the community plugin marketplace and what the marketplace does not protect against. Review it before changing plugin upload, approval, or runner behavior.
Roles¶
There are exactly two roles.
- Regular user. Anyone who has a valid OWTF account and an unexpired JWT. Regular users can upload plugins, browse the approved catalogue, see their own uploads (including rejection reasons), and run their own scans. They cannot see other users' uploads, cannot see any plugin's source code, and cannot approve, reject, delete, or read the review history of any plugin.
- Admin. A user with
users.is_admin = TRUEin the database, or whose email appears in theOWTF_ADMIN_EMAILSenv var at server start. Admins can review source, approve, reject, delete, and read the review history. Admins are the trust boundary for the whole marketplace: an approved plugin runs with the same permissions as a built-in OWTF plugin.
Admins can be seeded via OWTF_ADMIN_EMAILS (a comma-separated list of
emails) or managed at runtime with the owtf-admin CLI:
There is no default admin email. If OWTF_ADMIN_EMAILS is empty and
nobody has been promoted, the marketplace has no admins and no plugin
will ever be approved. That is intentional; a hard-coded default is a
credential leak waiting to happen.
Endpoint exposure¶
| Endpoint | Role | Notes |
|---|---|---|
POST /api/v1/community-plugins/upload |
user | AST-validated at upload time. |
GET /api/v1/community-plugins/ |
user | Approved-only. Non-admins get 403 on ?status=pending / ?status=rejected. |
GET /api/v1/community-plugins/<id>/ |
user | Public metadata. No file_path, no source_code. |
GET /api/v1/community-plugins/mine/ |
user | User's own uploads with rejection reasons. |
GET /api/v1/community-plugins/me/ |
user | Current user's {id, name, email, is_admin}. |
GET /api/v1/community-plugins/<id>/source/ |
admin | Reads file server-side; response never carries file_path. |
DELETE /api/v1/community-plugins/<id>/delete/ |
admin | Removes the DB row and the file on disk. |
POST /api/v1/community-plugins/<id>/approve/ |
admin | Records reviewer id + timestamp. |
POST /api/v1/community-plugins/<id>/reject/ |
admin | Body: {"reason": "..."}. Records reviewer id. |
GET /api/v1/community-plugins/<id>/review-history/ |
admin | Upload and review timeline built from the plugin row's own timestamps. Not an append-only audit log. |
Serializer discipline¶
Three serializers, one audience each:
to_dict: public. Metadata only. Never includesfile_path,source_code, reviewer identity, resource limits, or rejection reason.to_owner_dict: the uploader's own view.to_dict+rejection_reason+user_id.to_admin_dict: admin-only.to_owner_dict+ reviewer id + reviewed timestamp +execution_timeout. Source code is still not in this dict; admins fetch it through the source endpoint, which reads the file server-side.
file_path is a server-side implementation detail and must not appear
in any API response. There are pytest assertions
(TestSerializersNeverLeakFilePath) that fail if it ever does.
Upload validation¶
Uploads are checked in this order, and rejection at any step aborts the whole request without writing a DB row or a file:
- Extension is
.py. - Body size is within
PLUGIN_UPLOAD_MAX_SIZE. - Metadata (
name,description,group,type,author) is well-formed and within length limits. - Name is unique in the DB.
- AST validator (
owtf.plugin.validator.PluginValidator) accepts the code. This is the primary safety gate: it rejectsimport subprocess,os.system,exec,eval, dynamic imports, and a handful of other obviously-dangerous patterns.
On success the plugin is written to disk under COMMUNITY_PLUGINS_DIR
with a randomised suffix, marked pending, and returned to the
uploader in to_owner_dict shape.
What the sandbox does and does not protect against¶
The AST validator is a static check on plugin source. It catches common patterns that would obviously escape the plugin runtime, but it does not:
- run the code in a container or seccomp jail;
- limit filesystem or network access at the OS level;
- prevent a determined attacker from smuggling behaviour through
metaprogramming (
__import__,getattrchains, decoded strings); - protect against side effects triggered at import time.
The marketplace assumes an admin reviews the source before approval. Approval is the trust decision, and admins are expected to read the code, not just click a button. The current marketplace does not provide additional runtime sandboxing such as per-plugin containers or OS-enforced resource limits.
Upgrade behaviour¶
New installs get the schema from Model.metadata.create_all at
startup. Existing installs that already have a user_plugins table
pick up newly introduced columns via
owtf.db.upgrade.run_startup_upgrades, which runs ALTER TABLE ADD
COLUMN for every column the model expects but the table does not
have. It is idempotent, safe to call on every boot, and does not touch
existing data.
What is not implemented¶
- Rating and review UI for regular users.
- Plugin signing / verification of upload provenance.
- Runtime sandboxing beyond static AST validation.
- Multi-admin quorum for approval.