diff --git a/apify-api/openapi/paths/actors/acts@{actorId}@runs.yaml b/apify-api/openapi/paths/actors/acts@{actorId}@runs.yaml
index 3e39d6a6c..6ba29b3a7 100644
--- a/apify-api/openapi/paths/actors/acts@{actorId}@runs.yaml
+++ b/apify-api/openapi/paths/actors/acts@{actorId}@runs.yaml
@@ -275,6 +275,20 @@ post:
schema:
type: string
example: dGhpcyBpcyBqdXN0IGV4YW1wbGUK...
+ - name: forcePermissionLevel
+ in: query
+ description: |
+ Overrides the Actor's configured permission level for this run only. Useful for testing limited permissions
+ before migrating your Actor or temporarily elevating/restricting access. If not provided, the Actor's default
+ permission level is used. For more information on permissions see [the documentation](https://docs.apify.com/platform/actors/development/permissions)
+ style: form
+ explode: true
+ schema:
+ type: string
+ enum:
+ - LIMITED_PERMISSIONS
+ - FULL_PERMISSIONS
+ example: LIMITED_PERMISSIONS
requestBody:
description: ''
content:
diff --git a/sources/platform/actors/development/actor_definition/input_schema/specification.md b/sources/platform/actors/development/actor_definition/input_schema/specification.md
index 6e51c95a1..03ec3c2ab 100644
--- a/sources/platform/actors/development/actor_definition/input_schema/specification.md
+++ b/sources/platform/actors/development/actor_definition/input_schema/specification.md
@@ -861,7 +861,8 @@ Example of multiple datasets input:
"title": "Datasets",
"type": "array",
"description": "Select multiple datasets",
- "resourceType": "dataset"
+ "resourceType": "dataset",
+ "resourcePermissions": ["READ"]
}
```
@@ -876,6 +877,7 @@ Rendered input:
| `type` | `string` | Yes | Specifies the type of input - `string` for single value. |
| `editor` | One of
- `resourcePicker`
- `textfield`
- `hidden`
| No | Visual editor used for
the input field. Defaults to `resourcePicker`. |
| `resourceType` | One of - `dataset`
- `keyValueStore`
- `requestQueue`
| Yes | Type of Apify Platform resource |
+| `resourcePermissions` | Array of strings; allowed values: | Yes | Permissions requested for the referenced resource. Use [\"READ\"] for read-only access, or [\"READ\", \"WRITE\"] to allow writes.|
| `pattern` | String | No | Regular expression that will be used to validate the input. If validation fails, the Actor will not run. |
| `minLength` | Integer | No | Minimum length of the string. |
| `maxLength` | Integer | No | Maximum length of the string. |
@@ -887,5 +889,22 @@ Rendered input:
| `type` | `array` | Yes | Specifies the type of input - `array` for multiple values. |
| `editor` | One of | No | Visual editor used for
the input field. Defaults to `resourcePicker`. |
| `resourceType` | One of - `dataset`
- `keyValueStore`
- `requestQueue`
| Yes | Type of Apify Platform resource |
+| `resourcePermissions` | Array of strings; allowed values: | Yes | Permissions requested for the referenced resources. Use [\"READ\"] for read-only access, or [\"READ\", \"WRITE\"] to allow writes. Applies to each selected resource. |
| `minItems` | Integer | No | Minimum number of items the array can contain. |
| `maxItems` | Integer | No | Maximum number of items the array can contain. |
+
+#### Resource permissions
+
+If your Actor runs with limited permissions it needs to declare what kind of access needs to the resources supplied via the input. The `resourcePermissions` field expresses **what operations your Actor needs on the user-selected storages**. It is evaluated at run start and used to expand a [Limited-permissions Actors](../../permissions/index.md#) scope to be able to access the resource sent via Actor's input.
+
+- `["READ"]` — the Actor may read from the referenced resources.
+- `["READ", "WRITE"]` — the Actor may read and write to the referenced resources.
+
+:::note Keep in mind
+
+- This setting does not change field visibility or it being required in the UI; it only defines runtime access for the selected resources.
+- For array fields (`type: array`), the same permissions apply to **each** selected resource.
+- If your Actor attempts an operation without the requested permission (e.g., attempts to write with a read-only access), the run will fail with an insufficient-permissions error.
+- Your users will [see what permissions you require in the input's tooltip](../../../running/permissions.md#recognizing-permission-levels-in-console-and-store)
+
+:::
diff --git a/sources/platform/actors/development/permissions/images/actor_run_permission_override.png b/sources/platform/actors/development/permissions/images/actor_run_permission_override.png
new file mode 100644
index 000000000..24917d039
Binary files /dev/null and b/sources/platform/actors/development/permissions/images/actor_run_permission_override.png differ
diff --git a/sources/platform/actors/development/permissions/images/actor_settings_permissions.webp b/sources/platform/actors/development/permissions/images/actor_settings_permissions.webp
new file mode 100644
index 000000000..8256604e7
Binary files /dev/null and b/sources/platform/actors/development/permissions/images/actor_settings_permissions.webp differ
diff --git a/sources/platform/actors/development/permissions/images/end_user_ux_full_permissions.png b/sources/platform/actors/development/permissions/images/end_user_ux_full_permissions.png
new file mode 100644
index 000000000..19631e46b
Binary files /dev/null and b/sources/platform/actors/development/permissions/images/end_user_ux_full_permissions.png differ
diff --git a/sources/platform/actors/development/permissions/images/end_user_ux_limited_permissions.png b/sources/platform/actors/development/permissions/images/end_user_ux_limited_permissions.png
new file mode 100644
index 000000000..bce36bc8c
Binary files /dev/null and b/sources/platform/actors/development/permissions/images/end_user_ux_limited_permissions.png differ
diff --git a/sources/platform/actors/development/permissions/index.md b/sources/platform/actors/development/permissions/index.md
new file mode 100644
index 000000000..3a5b8c0e0
--- /dev/null
+++ b/sources/platform/actors/development/permissions/index.md
@@ -0,0 +1,126 @@
+---
+title: Permissions
+description: Learn how to declare and manage permissions for your Actor, what access levels mean, and how to build secure, trusted Actors for Apify users.
+sidebar_position: 7.5
+slug: /actors/development/permissions
+---
+
+**Learn how to declare and manage permissions for your Actor, what permission levels mean, and how to build secure, trusted Actors for Apify users.**
+
+---
+
+Every time a user runs your Actor, it runs under their Apify account. **Actor Permissions** is an Actor level setting that defines the level of access your Actor needs to be able to run. This gives users transparency and control over what data your Actor can access, building trust in your tools.
+
+There are two levels of access your Actors can request:
+
+- **Limited permissions (default):** Actors with this permission level have restricted access, primarily to their own storages and the data they generate. They cannot access other user data on the Apify platform.
+- **Full permissions:** This level grants an Actor access to all of a user's Apify account data.
+
+Most Actors should use limited permissions to request only the specific access they need and reserve full permissions for exceptional cases where the Actor cannot function otherwise.
+
+## How Actor permissions work
+
+When a user runs an Actor, it receives an Apify API token. This token is injected to the Actor's runtime and has a scope of access as requested by the Actor's permission level.
+
+Actors with **full permissions** receive a token that grants complete access to the user's Apify account via the Apify API.
+
+Actors with **limited permissions** receive [a restricted scoped token](../../../integrations/programming/api.md#api-tokens-with-limited-permissions). This token only allows the Actor to perform a specific set of actions, which covers the vast majority of common use cases.
+
+ A limited-permission Actor can:
+
+- Read and write to its default storages.
+- Update the current run’s status, abort the run, or [metamorph](../programming_interface/metamorph.md) to another Actor (as long as it also has limited permissions).
+- Read basic user information (whether the user is paying, their proxy password or public profile) from the environment.
+- Read and/or write to storages provided via Actor input (sample scenario: the user provides the Actor with a dataset that the Actor should write into).
+- Run any other Actor with limited permissions.
+- Create any additional storage, and write to that storage.
+- Read and write to storages created in previous runs.
+
+This approach ensures your Actor has everything it needs to function while protecting user data from unnecessary exposure.
+
+:::info
+
+To learn how to migrate your Actors to run under limited permissions, see the [Migration guide](./migration_guide.md)
+
+:::
+
+### Configuring Actor permissions level
+
+You can set the permission level for your Actor in the Apify Console under its **Settings** tab. All the existing Actors are configured to use full permissions, but the plan is to make limited permissions the default for all new Actors.
+
+
+
+### End-user experience
+
+Currently, users will see a visible permission badge on your Actor's detail page indicating whether it requires "Limited permissions" or "Full permissions". At this stage, the experience of running an Actor will not change for the user.
+
+
+
+
+
+:::warning
+
+Whenever possible, design your Actors to use limited permissions and request only access they truly need. Actors requiring full permissions may receive a lower [Actor Quality score](../../publishing/quality_score.mdx), which can reduce their ranking in the store.
+
+:::
+
+
+### Accessing user provided storages
+
+By default, limited-permissions Actors can't access user storages. However, they can access storages that users explicitly provide via the Actor input. To do so, use the input schema to add a storage picker and declare exactly which operations your Actor needs.
+
+- Add a field with `editor: "resourcePicker"`.
+- Set `resourceType` to one of `dataset`, `keyValueStore`, or `requestQueue`.
+- Specify `resourcePermissions` with the minimal required scope: `"READ"` or `"READ", "WRITE"`.
+
+Actors running under full permissions are assumed to have full (i.e. read/write/manage) access to user storages. In that case the `resourcePermissions` field does not have to be set.
+
+Example input schema field (single resource):
+
+```json
+{
+ "title": "Output dataset",
+ "type": "string",
+ "editor": "resourcePicker",
+ "resourceType": "dataset",
+ "resourcePermissions": ["READ", "WRITE"]
+}
+```
+
+Selecting multiple resources:
+
+- Use `type: "array"` to let users choose more than one storage.
+- Each selected resource will have the same `resourcePermissions`
+
+```json
+{
+ "title": "Source datasets",
+ "type": "array",
+ "editor": "resourcePicker",
+ "resourceType": "dataset",
+ "resourcePermissions": ["READ"],
+ "minItems": 1
+}
+```
+
+Behavior at run time:
+
+- The user’s selection is injected into the run input, and the run token is expanded to allow only the requested operations on the selected storages.
+- If your code attempts an operation not covered by `resourcePermissions` (for example, writing with only `READ`), the platform returns an insufficient-permissions error.
+
+See the full [input schema reference for details.](../actor_definition/input_schema/specification.md).
+
+### Requesting full permissions
+
+Designing your Actors to work under limited permissions is the recommended approach, it helps improve your Actor’s [Actor Quality score](../../publishing/quality_score.mdx#trustworthiness) and increases user trust and adoption. However, some use cases do legitimately require broader access to user data (e.g., to perform administrative tasks or orchestrate other Actors). If your Actor falls in this category or cannot function with limited permissions for another reason:
+
+- Explain why you need full permissions in your Actor's README. This will help keep user trust and set correct user expectations.
+- Set the permission level in the Actor’s **Settings** in Console to **Full permissions**.
+- Be aware of the [UX implications](#end-user-experience) and impact on [Actor Quality score](../../publishing/quality_score.mdx) for full-permission Actors.
+
+
+:::info
+
+Actor permissions are a new feature. If something is preventing you from migrating to limited permissions or you have a use case that you think should work under limited permissions and it does not, please reach out to support or talk to us on [the community forum](https://discord.gg/eN73Xdhtqc).
+
+:::
diff --git a/sources/platform/actors/development/permissions/migration_guide.md b/sources/platform/actors/development/permissions/migration_guide.md
new file mode 100644
index 000000000..3cb71c311
--- /dev/null
+++ b/sources/platform/actors/development/permissions/migration_guide.md
@@ -0,0 +1,148 @@
+---
+title: Migration guide
+description: How to migrate your Actor to limited permissions. Common migration paths, code examples, and common issues.
+sidebar_position: 9
+slug: /actors/development/permissions/migration-guide
+---
+
+**Migrate your Actor to limited permissions, review common migration paths, code examples, and solutions to common issues.**
+
+---
+
+Use this guide to migrate existing Actors to use [limited permissions](index.md#how-actor-permissions-work). The general prerequisite is to update to the latest [Apify SDK](https://docs.apify.com/sdk).
+
+Recommended minimum SDK versions:
+
+- JavaScript SDK: [apify@3.4.4](https://github.com/apify/apify-sdk-js/releases/tag/apify%403.4.4)
+- Python SDK: [v3.0.0](https://github.com/apify/apify-sdk-python/releases/tag/v3.0.0)
+
+Before you start it's helpful to understand [what access restrictions limited permissions impose](index.md#how-actor-permissions-work).
+
+## How to test your Actor with limited permissions before migrating
+
+You can override permission level for a single run using run options under Actor Source tab in Console:
+
+
+
+You can do the same using the Apify Client as well:
+
+```tsx
+await apifyClient.actor(actorId).call(input, {
+ forcePermissionLevel: ACTOR_PERMISSION_LEVEL.LIMITED_PERMISSIONS,
+});
+```
+
+Or just using the API:
+
+```tsx
+ POST https://api.apify.com/v2/acts//runs?**forcePermissionLevel=LIMITED_PERMISSIONS**
+```
+
+
+## Common migration paths
+
+We expect that most public Actors can be migrated to limited permissions with minor, if any, adjustments. The general prerequisite is to **update the Actor to use the latest [Apify SDK](https://docs.apify.com/sdk)**. To assess what, if anything, needs to change in your Actor, review these areas:
+
+- How your actor uses storages (e.g. named storages and storages provided via input)
+- Whether it requests correct resource access for user-provided storages
+- Whether it needs to access information about the user (namely if the user is paying or access their proxy configuration)
+
+Once you have updated and [tested](#how-to-test-your-actor-with-limited-permissions-before-migrating) your Actor, you can change the permissions in the Actor settings. The setting will take immediate effect.
+
+Below you can read a guide covering common migration paths for more advanced cases in greater detail.
+
+### The Actor only pushes data to default storages
+
+This is the most common and simplest use case. If your Actor only reads its input and writes results to its default dataset, key-value store, or request queue, **no changes are needed**. Limited permissions fully support this behavior out of the box.
+
+### The Actor calls other Actors
+
+An Actor with limited permissions can only call other Actors that also have limited permissions. If your Actor calls another one, you will need to ensure the target Actor has been migrated first.
+
+### The Actor accesses storages provided by the user
+
+If your Actor is designed to read from or write to a storage that the user provides via an input field, you must explicitly declare what access you need in Actor’s schema.
+
+1. Populate `resourceType` property on the field to enable the native resource picker.
+2. Populate `resourcePermissions` with permissions you need for the resource.
+
+Let’s say your Actor allows the user to provide a custom dataset that your Actor should output its result to. Your `input_schema.json` might contain something like this:
+
+```json
+{
+ "title": "Output",
+ "type": "string",
+ "description": "Select a dataset for the Actor results",
+}
+```
+
+To support limited permissions, change it to this:
+
+```json
+{
+ "title": "Output",
+ "type": "string",
+ "description": "Select a dataset for the Actor results",
+ "resourceType": "dataset",
+ "resourcePermissions": ["READ", "WRITE"],
+ "editor": "textfield", // If you want to preserve the plain "string" input UI, instead of rich resource picker.
+}
+```
+
+Now when the platform runs your Actor, it’ll automatically add the user provided storage to the Actor’s scope so that it can access it.
+
+:::info Backward compatibility
+
+The user can provide the resource both via its name and its ID. If you have existing users with existing inputs that specify the resource via its name, this change to the input schema won’t break it.
+
+:::
+
+### The Actor accesses named storages
+
+Actors sometimes use named storages for caching or persisting state across runs. With limited permissions, an Actor can create a named storage on its first run and will automatically retain access to it in all subsequent runs by the same user.
+
+If your Actor previously relied on accessing a pre-existing named storage, you will need to rename it in your code. This will cause the Actor to recreate the storage under the new system on its next run.
+
+In order to achieve a smooth migration without disrupting your Actor’s users, you will need to make sure that your Actor can handle the transition. The suggested approach is the following:
+
+1. Adjust the code of the Actor so that it can run with both limited and full permissions.
+2. Change the Actor setting to limited permissions.
+3. Clean up the migration code.
+
+```ts
+const OLD_CACHE_STORE_NAME = 'my-actor-cache';
+const NEW_CACHE_STORE_NAME = 'my-actor-cache-updated';
+
+let store;
+
+if (process.env.ACTOR_PERMISSION_LEVEL === 'LIMITED_PERMISSIONS') {
+ // If the Actor is running with limited permissions and we need to create
+ // a new store. The platform will remember that the store was created by this Actor
+ // and will allow access in all follow-up runs.
+ store = await Actor.openKeyValueStore(NEW_CACHE_STORE_NAME);
+} else {
+ // If the Actor is still running with full permissions and we should use
+ // the existing store.
+ store = await Actor.openKeyValueStore(OLD_CACHE_STORE_NAME);
+}
+```
+
+:::info Re-create cache only under limited permissions.
+
+The goal here is to create the new storage **only once the Actor runs with limited permissions**. Only that way the access is retained in follow-up runs.
+
+:::
+
+If the existing contents of the named storage are critical for your Actor to keep functioning for the existing users and it is impossible, costly or highly impractical to migrate, contact support or reach out to us [on the community forum](https://discord.gg/eN73Xdhtqc). We can discuss the available options.
+
+### The Actor needs to know whether the user is paying
+
+Some Actors have different logic for free and paying users. Previously you could retrieve this information by calling the `/users/me` API endpoint. However, Actors running under limited permissions don't have access to that endpoint. To get this information, your Actor should read the `APIFY_USER_IS_PAYING` environment variable, or directly use the SDK to obtain the value:
+
+```ts
+const { userIsPaying } = Actor.getEnv();
+```
+
+### The Actor uses Proxy
+
+Similarly, if your Actor uses [Proxy](../../../proxy/index.md) and needs to retrieve the user's proxy password, it should get it from the `APIFY_PROXY_PASSWORD` environment variable instead of calling the `/users/me` endpoint or, preferably, rely directly on the SDK to handle proxy configuration automatically.
diff --git a/sources/platform/actors/development/programming_interface/environment_variables.md b/sources/platform/actors/development/programming_interface/environment_variables.md
index 7830d19eb..2d2a3eedb 100644
--- a/sources/platform/actors/development/programming_interface/environment_variables.md
+++ b/sources/platform/actors/development/programming_interface/environment_variables.md
@@ -54,6 +54,7 @@ Here's a table of key system environment variables:
| `APIFY_HEADLESS` | If **1**, web browsers inside the Actor should run in headless mode (no windowing system available). |
| `APIFY_IS_AT_HOME` | Contains **1** if the Actor is running on Apify servers. |
| `ACTOR_MEMORY_MBYTES` | Size of memory allocated for the Actor run, in megabytes. Can be used to optimize memory usage or finetuning of low-level external libraries. |
+| `ACTOR_PERMISSION_LEVEL` | [Permission level](../../running/permissions.md) the Actor is run under. This determines what resources in the user’s account the Actor can access. |
| `APIFY_PROXY_PASSWORD` | Password for accessing Apify Proxy services. This password enables the Actor to utilize proxy servers on behalf of the user who initiated the Actor run. |
| `APIFY_PROXY_PORT` | TCP port number to be used for connecting to the Apify Proxy. |
| `APIFY_PROXY_STATUS_URL` | URL for retrieving proxy status information. Appending `?format=json` to this URL returns the data in JSON format for programmatic processing. |
diff --git a/sources/platform/actors/development/programming_interface/metamorph.md b/sources/platform/actors/development/programming_interface/metamorph.md
index 91b0b9564..90b9fec4e 100644
--- a/sources/platform/actors/development/programming_interface/metamorph.md
+++ b/sources/platform/actors/development/programming_interface/metamorph.md
@@ -41,6 +41,12 @@ There's a limit to how many times you can metamorph a single run. Refer to the [
:::
+:::note Permissions
+
+Actors running under limited permissions can only metamorph into Actors that also run with limited permissions. See [how Actor permissions work](../../running/permissions#how-actor-permissions-work) for details.
+
+:::
+
## Example
Let's walk through an example of using metamorph to create a hotel review scraper:
diff --git a/sources/platform/actors/publishing/quality_score.mdx b/sources/platform/actors/publishing/quality_score.mdx
index b25d9ccbf..7764462c4 100644
--- a/sources/platform/actors/publishing/quality_score.mdx
+++ b/sources/platform/actors/publishing/quality_score.mdx
@@ -65,6 +65,14 @@ Pricing transparency evaluates how clearly users can understand and predict the
Consider offering discounts for Bronze, Silver, and Gold subscription tiers. These incentives reward committed platform users and can increase your Actor's adoption among engaged customers.
+### Trustworthiness
+
+Trustworthiness evaluates whether your Actor follows the principle of least privilege by using limited permissions. This configuration provides clear boundaries around what the Actor can access, demonstrating adherence to platform security standards.
+
+Actors with [limited permissions](../development/permissions#how-actor-permissions-work) build user confidence and trust, making users more likely to adopt them. Conversely, the absence of limited permissions will negatively impact your quality score.
+
+Limited permissions support the vast majority of Actor use cases without restricting functionality. If your Actor requires access to resources beyond what limited permissions provide, contact the Apify support team to discuss your specific requirements.
+
### History of success
Developers with a proven track record of publishing successful Actors receive recognition in their quality scores. This factor acknowledges the value of experienced developers who consistently deliver high-quality Actors to the platform.
diff --git a/sources/platform/actors/running/permissions.md b/sources/platform/actors/running/permissions.md
new file mode 100644
index 000000000..727bb9643
--- /dev/null
+++ b/sources/platform/actors/running/permissions.md
@@ -0,0 +1,60 @@
+---
+title: Permissions
+description: "Learn how Actor permissions work for running and building Actors: available permission levels, requesting and granting permissions, and security best practices."
+sidebar_position: 5
+slug: /actors/running/permissions
+---
+
+**Learn how permissions work when running and building Actors, what permission levels mean, how to request, grant, and review permissions, and how to keep your data and projects secure.**
+
+---
+
+When you run an Actor, it executes under your Apify account and may need access to your data to complete its task. Actor permissions define how much of that data the Actor can access. Each Actor declares its required permission level in its configuration, and the platform enforces this level at runtime.
+
+::::note
+The approach is similar to mobile platforms (Android, iOS) where each app has to explicitly request access it needs and the user has to approve it. The difference is that instead of granular per-Actor permissions, we chose just two broad permission levels which should cover the vast majority of use cases. If you are a developer, see the [development guide on Actor permissions](../development/permissions) to learn how to declare and manage permissions for your Actors.
+::::
+
+The permissions model follows the principle of least privilege. Actors run only with the access they explicitly request, giving users transparency and control over what the Actor can access in their account.
+
+There are two permission levels:
+
+- **Limited permissions (default)** — Actors with this permission level have restricted access, primarily to their own storages, the data they generate and resources they are given an explicit access to. They cannot access any other data in your Apify account.
+- **Full permissions** — grants the Actor a access to all data in your Apify account.
+
+
+This model protects your data and strengthens platform security by clearly showing what level of access each Actor requires.
+
+Actors using **limited permissions** are safer to run and are suitable for most tasks. Actors that need **full permissions** (for example to perform administrative tasks in your account, manage your datasets or schedules) clearly indicate this in their detail page so you can make an informed choice.
+
+
+## How Actor permissions work
+
+When a user runs an Actor, it receives an Apify API token. Traditionally, this token grants access to the user's entire Apify account via Apify API. Actors with **full permissions** will continue to operate this way.
+
+Actors with **limited permissions** receive a restricted token. This token only allows the Actor to perform a specific set of actions, which covers the vast majority of common use cases. A limited-permission Actor can:
+
+- Read and write to its default storages.
+- Update the current run’s status, abort the run, or [metamorph](../development/programming_interface/metamorph.md) to another Actor (as long as it also has limited permissions).
+- Read basic user information (whether the user is paying, proxy password, public profile) from the environment.
+- Read or also write to storages provided via Actor input (sample scenario: the user provides the Actor with a dataset that the Actor should write into).
+- Run any other Actor with limited permissions.
+- Create any additional storage, and write to that storage.
+- Read and write to storages created in previous runs.
+
+This approach ensures the Actor has everything it needs to function while protecting your data from unnecessary exposure.
+
+
+### Recognizing permission levels in Console and Store
+
+When you browse Actors in Apify Console or Store, you’ll notice a small badge next to each Actor showing its permission level. Hover over the badge to see a short explanation of what access that Actor will have when it runs under your account. Here's how they appear in the Console.
+
+**Limited permissions Actors:**
+
+
+
+**Full permissions Actors:**
+
+
+
+Over time, the distinction between these permission levels will become more prominent. For example, Actors requiring full permissions may display an orange badge, appear lower in Store rankings, or show extra confirmation dialogs when you run them. Whenever possible, choose Actors that use **limited permissions**. They are safer, easier to trust, and sufficient for most workflows.