# What an extension is trusted with

Read this before installing anything, and before publishing anything.

## The short version

**An installed extension runs as PHP in the same process as Convoro, with the
same reach: your database, your filesystem, your network, your members'
sessions and their personal data. There is no sandbox.**

That is a deliberate decision, not an oversight, and it is not going to change.
A sandbox PHP could actually enforce would have to forbid so much that no
useful extension could be written, and an advisory one is worse than none
because it implies a guarantee that is not there. Flarum, IPS, XenForo and
WordPress all take the same position. Convoro says it out loud instead of
leaving you to work it out.

**So the decision you are making when you install an extension is whether you
trust its author with everything.** Not whether you trust the feature.

## What Convoro does do

None of this makes an extension safe. It makes the *supply* of extensions
harder to interfere with, which is a different and smaller thing.

**Only administrators can install.** The admin routes are behind
`RequireAdmin`, and the CLI needs shell access.

**Extraction cannot escape the extension's own directory.** Entry names are
refused if they are absolute, use backslashes, name a drive, contain a null
byte or contain a `..` segment; the resolved parent directory is checked
against the extensions root before every single write. A `.zip` cannot write
to `engine/`, `public/` or anywhere else. Removal applies the same check in
reverse, and unlinks symlinks rather than following them out.

**A package cannot be a zip bomb.** There is a ceiling on what an archive may
expand to, and it is checked as entries are read.

**Extensions live in their own tree.** `extensions/` is separate from
`modules/`, so an uninstall has exactly one directory it is allowed to touch
and that is checkable in one comparison.

**Nothing loads because it exists.** A directory under `extensions/` runs only
if `cv_extensions` has an enabled row for it, so a half-finished upload never
executes and a directory copied in by FTP does nothing at all. If that check
cannot be made — database down mid-boot — no extensions load.

**The package hash is recorded at install.** Files changing under a running
site afterwards is then detectable rather than invisible.

## Signing

An author can sign a package, and a site can refuse packages that are not
signed by a key it trusts:

```php
// config/extensions.php
'require_signature' => true,
'trusted_keys' => [
    'Some Author' => "-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----",
],
```

**A signature answers "who built this". It never answers "what does it do".**
A signed extension from an author you trust still runs in-process with the same
reach as core. Signing narrows *who* can put code on your server; it says
nothing about what that code then does.

The signature covers a digest of the package contents rather than the zip file
itself, so re-compressing or repacking does not invalidate a good signature,
and adding a file to a signed package does.

`require_signature` is off by default. Turning it on with no keys configured
would refuse every install, and a default people have to switch off to get
anything done teaches them to switch it off.

## If you are installing

- Install from people you would trust with your database, because that is what
  you are doing.
- Prefer packages whose source you can read. A `.zip` is a zip — rename it and
  open it.
- Take a database backup before installing something new. Migrations run on
  install.
- After an upgrade, look at the site. An extension that breaks a page is far
  more common than one that is malicious.

## If you are publishing

- Escape everything from the database before it goes into a hook's return
  value. A hook returns HTML and nothing escapes it for you. This is the most
  likely way a well-meant extension introduces stored XSS.
- Use the query builder's bindings. Never interpolate user input into SQL.
- Verify CSRF on every state-changing route, and never change state on a GET.
- Check permissions in your controller. Routes and hooks do not do it for you —
  `topic.moderation.actions` only renders for moderators, but a route you add
  is open to anyone unless you say otherwise.
- Never take a route parameter on trust. That it matched means it was the right
  shape, not that it points at something the member may see.
- Write `down()` as if somebody will run it, because they will.
- Say in your README what your extension talks to over the network, and what
  data it sends. People deserve to know that before installing, not after.
