mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
cdm lookups used the raw service key while decryption upper-cases its keys, so a case-mismatched cdm entry silently used the default. Add a shared ci_get helper, route cdm/decryption through resolvers, drop a redundant decryption mutation and dead downloader writes, and add a regression bench.
585 lines
22 KiB
Markdown
585 lines
22 KiB
Markdown
# DRM & CDM Configuration
|
||
|
||
This document covers Digital Rights Management (DRM) and Content Decryption Module (CDM) configuration options.
|
||
|
||
## cdm (dict)
|
||
|
||
Pre-define which Widevine or PlayReady device to use for each Service by Service Tag as Key (case-insensitive).
|
||
The value should be a WVD or PRD filename without the file extension. When
|
||
loading the device, unshackle will look in both the `WVDs` and `PRDs` directories
|
||
for a matching file.
|
||
|
||
For example,
|
||
|
||
```yaml
|
||
EXAMPLE: chromecdm_903_l3
|
||
EXAMPLE2: nexus_6_l1
|
||
```
|
||
|
||
You may also specify this device based on the profile used.
|
||
|
||
For example,
|
||
|
||
```yaml
|
||
EXAMPLE: chromecdm_903_l3
|
||
EXAMPLE2: nexus_6_l1
|
||
EXAMPLE3:
|
||
john_sd: chromecdm_903_l3
|
||
jane_uhd: nexus_5_l1
|
||
```
|
||
|
||
You can also specify a fallback value to predefine if a match was not made.
|
||
This can be done using `default` key. This can help reduce redundancy in your specifications.
|
||
|
||
For example, the following has the same result as the previous example, as well as all other
|
||
services and profiles being pre-defined to use `chromecdm_903_l3`.
|
||
|
||
```yaml
|
||
EXAMPLE2: nexus_6_l1
|
||
EXAMPLE3:
|
||
jane_uhd: nexus_5_l1
|
||
default: chromecdm_903_l3
|
||
```
|
||
|
||
You can also select CDMs based on video resolution using comparison operators (`>=`, `>`, `<=`, `<`)
|
||
or exact match on the resolution height.
|
||
|
||
For example,
|
||
|
||
```yaml
|
||
EXAMPLE:
|
||
"<=1080": generic_android_l3 # Use L3 for 1080p and below
|
||
">1080": nexus_5_l1 # Use L1 for above 1080p (1440p, 2160p)
|
||
default: generic_android_l3 # Fallback if no quality match
|
||
```
|
||
|
||
You can mix profiles and quality thresholds in the same service:
|
||
|
||
```yaml
|
||
EXAMPLE:
|
||
john: example_l3_profile # Profile-based selection
|
||
"<=720": example_mobile_l3 # Quality-based selection
|
||
"1080": example_standard_l3 # Exact match for 1080p
|
||
">=1440": example_premium_l1 # Quality-based selection
|
||
default: example_standard_l3 # Fallback
|
||
```
|
||
|
||
---
|
||
|
||
## remote_cdm (list\[dict])
|
||
|
||
Configure remote CDM (Content Decryption Module) APIs to use for decrypting DRM-protected content.
|
||
Remote CDMs allow you to use high-security CDMs (L1/L2 for Widevine, SL2000/SL3000 for PlayReady) without
|
||
having the physical device files locally.
|
||
|
||
unshackle supports multiple types of remote CDM providers:
|
||
|
||
1. **DecryptLabs CDM** - Official DecryptLabs KeyXtractor API with intelligent caching
|
||
2. **Custom API CDM** - Highly configurable adapter for any third-party CDM API
|
||
3. **Legacy PyWidevine Serve** - Standard pywidevine serve-compliant APIs
|
||
|
||
The name of each defined remote CDM can be referenced in the `cdm` configuration as if it was a local device file.
|
||
|
||
### DecryptLabs Remote CDM
|
||
|
||
DecryptLabs provides a professional CDM API service with support for multiple device types and intelligent key caching.
|
||
|
||
**Supported Devices:**
|
||
- **Widevine**: `ChromeCDM` (L3), `L1` (Security Level 1), `L2` (Security Level 2)
|
||
- **PlayReady**: `SL2` (SL2000), `SL3` (SL3000)
|
||
|
||
**Configuration:**
|
||
|
||
```yaml
|
||
remote_cdm:
|
||
# Widevine L1 Device
|
||
- name: decrypt_labs_l1
|
||
type: decrypt_labs # Required: identifies as DecryptLabs CDM
|
||
device_name: L1 # Required: must match exactly (L1, L2, ChromeCDM, SL2, SL3)
|
||
host: https://keyxtractor.decryptlabs.com
|
||
secret: YOUR_API_KEY # Your DecryptLabs API key
|
||
|
||
# Widevine L2 Device
|
||
- name: decrypt_labs_l2
|
||
type: decrypt_labs
|
||
device_name: L2
|
||
host: https://keyxtractor.decryptlabs.com
|
||
secret: YOUR_API_KEY
|
||
|
||
# Chrome CDM (L3)
|
||
- name: decrypt_labs_chrome
|
||
type: decrypt_labs
|
||
device_name: ChromeCDM
|
||
host: https://keyxtractor.decryptlabs.com
|
||
secret: YOUR_API_KEY
|
||
|
||
# PlayReady SL2000
|
||
- name: decrypt_labs_playready_sl2
|
||
type: decrypt_labs
|
||
device_name: SL2
|
||
device_type: PLAYREADY # Required for PlayReady
|
||
host: https://keyxtractor.decryptlabs.com
|
||
secret: YOUR_API_KEY
|
||
|
||
# PlayReady SL3000
|
||
- name: decrypt_labs_playready_sl3
|
||
type: decrypt_labs
|
||
device_name: SL3
|
||
device_type: PLAYREADY
|
||
host: https://keyxtractor.decryptlabs.com
|
||
secret: YOUR_API_KEY
|
||
```
|
||
|
||
**Features:**
|
||
- Intelligent key caching system (reduces API calls)
|
||
- Automatic integration with unshackle's vault system
|
||
- Support for both Widevine and PlayReady
|
||
- Multiple security levels (L1, L2, L3, SL2000, SL3000)
|
||
|
||
**Note:** The `device_type` field determines whether the CDM operates in PlayReady or Widevine mode.
|
||
Setting `device_type: PLAYREADY` (or using `device_name: SL2` / `SL3`) activates PlayReady mode.
|
||
The `security_level` field is auto-computed from `device_name` when not specified (e.g., SL2 defaults
|
||
to 2000, SL3 to 3000, and Widevine devices default to 3). You can override these if needed.
|
||
|
||
### Custom API Remote CDM
|
||
|
||
A highly configurable CDM adapter that can work with virtually any third-party CDM API through YAML configuration.
|
||
This allows you to integrate custom CDM services without writing code.
|
||
|
||
**Basic Example:**
|
||
|
||
```yaml
|
||
remote_cdm:
|
||
- name: custom_chrome_cdm
|
||
type: custom_api # Required: identifies as Custom API CDM
|
||
host: https://your-cdm-api.com
|
||
timeout: 30 # Optional: request timeout in seconds
|
||
|
||
device:
|
||
name: ChromeCDM
|
||
type: CHROME # CHROME, ANDROID, PLAYREADY
|
||
system_id: 27175
|
||
security_level: 3
|
||
|
||
auth:
|
||
type: bearer # bearer, header, basic, body
|
||
key: YOUR_API_TOKEN
|
||
|
||
endpoints:
|
||
get_request:
|
||
path: /get-challenge
|
||
method: POST
|
||
decrypt_response:
|
||
path: /get-keys
|
||
method: POST
|
||
|
||
caching:
|
||
enabled: true # Enable key caching
|
||
use_vaults: true # Integrate with vault system
|
||
```
|
||
|
||
**Full field reference:**
|
||
|
||
```yaml
|
||
remote_cdm:
|
||
- name: advanced_custom_api
|
||
type: custom_api
|
||
host: https://api.example.com
|
||
timeout: 30 # Default request timeout (seconds); overridden per-endpoint
|
||
|
||
device:
|
||
name: L1 # Passed as 'scheme' in base request params
|
||
type: ANDROID # CHROME, ANDROID, or PLAYREADY
|
||
system_id: 26830 # Widevine system ID (informational; not sent)
|
||
security_level: 1 # 1–3 for Widevine; 2000/3000 for PlayReady
|
||
|
||
# Authentication — applied to every request via session headers or Basic auth
|
||
auth:
|
||
type: header # bearer | header | basic | body | query
|
||
header_name: X-API-Key # used when type=header (default: Authorization)
|
||
key: YOUR_SECRET_KEY # API key / token / password
|
||
# bearer_token: TOKEN # explicit token for type=bearer (falls back to 'key')
|
||
# username: user # required for type=basic
|
||
# password: pass # required for type=basic
|
||
custom_headers: # merged into every request
|
||
User-Agent: Unshackle/3.1.0
|
||
X-Client-Version: "1.0"
|
||
|
||
# Endpoints — both keys are required
|
||
endpoints:
|
||
get_request: # first call: obtain challenge or cached keys
|
||
path: /v2/challenge
|
||
method: POST
|
||
timeout: 30 # overrides top-level timeout for this endpoint
|
||
decrypt_response: # second call: exchange license response for keys
|
||
path: /v2/decrypt
|
||
method: POST
|
||
timeout: 30
|
||
|
||
# request_mapping — transform outgoing parameters before each request
|
||
# Base params sent to get_request: scheme, init_data, [service], [service_certificate]
|
||
# Base params sent to decrypt_response: scheme, session_id, init_data, license_request, license_response
|
||
request_mapping:
|
||
get_request:
|
||
param_names: # rename base param keys
|
||
init_data: pssh
|
||
scheme: device_type
|
||
static_params: # always-added fixed values
|
||
api_version: "2.0"
|
||
conditional_params: # added only when condition is true
|
||
- condition: "is_playready == True"
|
||
params:
|
||
drm_type: playready
|
||
transforms: # applied after renaming; type values listed below
|
||
- param: pssh
|
||
type: base64_encode
|
||
nested_params: # group named params under a parent key
|
||
payload:
|
||
- pssh
|
||
- device_type
|
||
exclude_params: # remove unwanted params before sending
|
||
- service_certificate
|
||
decrypt_response:
|
||
param_names:
|
||
license_request: challenge
|
||
license_response: license
|
||
|
||
# response_mapping — extract and validate fields from API responses
|
||
response_mapping:
|
||
get_request:
|
||
fields: # map standard names to dot-path locations in response JSON
|
||
challenge: data.challenge
|
||
session_id: session.id
|
||
message: status.message
|
||
response_types: # classify response so CDM can choose the right path
|
||
- condition: "message_type == 'license-request'"
|
||
type: license_request # CDM expects challenge + session_id
|
||
- condition: "message_type == 'cached-keys'"
|
||
type: cached_keys # CDM skips license round-trip
|
||
success_conditions: # all must be true; failure raises RequestException
|
||
- "message == 'success'"
|
||
error_fields: # inspected when success_conditions fail (default: error, message, details)
|
||
- error
|
||
- details
|
||
transforms: # applied to extracted fields
|
||
- field: challenge
|
||
type: base64_decode
|
||
decrypt_response:
|
||
fields:
|
||
keys: data.keys
|
||
message: status.message
|
||
key_fields: # map key-object property names to standard names
|
||
kid: key_id # default: kid
|
||
key: content_key # default: key
|
||
type: key_type # default: type (value used as-is; CONTENT treated as content key)
|
||
success_conditions:
|
||
- "message == 'success'"
|
||
|
||
# caching — vault and API-level key caching
|
||
caching:
|
||
enabled: true # master switch (default: true)
|
||
use_vaults: true # check/write unshackle key vaults (default: true)
|
||
check_cached_first: true # check vaults before making any API call (default: true)
|
||
```
|
||
|
||
**Supported `transforms` types** (for both `request_mapping` and `response_mapping`):
|
||
`base64_encode`, `base64_decode`, `hex_encode`, `hex_decode`, `json_stringify`, `json_parse`, `parse_key_string`
|
||
|
||
**Supported authentication types:**
|
||
- `bearer` — `Authorization: Bearer <key>` (or `bearer_token` field)
|
||
- `header` — arbitrary header; set `header_name` (default `Authorization`)
|
||
- `basic` — HTTP Basic auth via `username` / `password`
|
||
- `body` — credentials injected into request body (handled by request_mapping)
|
||
- `query` — credentials appended to query string (handled by request_mapping)
|
||
|
||
### PyPlayReady Remote CDM
|
||
|
||
Connects to a running `unshackle serve` instance for PlayReady licensing. Uses
|
||
`pyplayready.remote.remotecdm.RemoteCdm` under the hood. No `type` field is needed —
|
||
the entry is routed to PlayReady when `device_type: PLAYREADY` is set (same branching
|
||
logic as the legacy Widevine path).
|
||
|
||
```yaml
|
||
remote_cdm:
|
||
- name: playready_remote
|
||
device_name: my_prd_device # Device name registered on the serve instance
|
||
device_type: PLAYREADY # Required to select the PlayReady code path
|
||
system_id: 0 # Not used by pyplayready RemoteCdm; set to 0
|
||
security_level: 3000 # 2000 = SL2000, 3000 = SL3000
|
||
host: http://127.0.0.1:8786/playready # Must include the /playready path suffix
|
||
secret: your-api-secret-key # X-Secret-Key sent to the serve instance
|
||
```
|
||
|
||
**Note:** The `/playready` path suffix in `host` is required — `pyplayready.RemoteCdm`
|
||
appends its own endpoint paths on top of this base, so omitting the suffix will result
|
||
in 404 errors.
|
||
|
||
### Legacy PyWidevine Serve Format
|
||
|
||
Standard [pywidevine] serve-compliant remote CDM configuration (backwards compatibility).
|
||
|
||
```yaml
|
||
remote_cdm:
|
||
- name: legacy_chrome_cdm
|
||
device_name: chrome
|
||
device_type: CHROME
|
||
system_id: 27175
|
||
security_level: 3
|
||
host: https://domain.com/api
|
||
secret: secret_key
|
||
```
|
||
|
||
**Note:** If the `type` field is not specified, the entry is treated as a legacy pywidevine serve CDM.
|
||
|
||
[pywidevine]: https://github.com/rlaphoenix/pywidevine
|
||
|
||
---
|
||
|
||
## decrypt_labs_api_key (str)
|
||
|
||
API key for DecryptLabs CDM service integration.
|
||
|
||
When set, enables the use of DecryptLabs remote CDM services in your `remote_cdm` configuration.
|
||
This is used specifically for `type: "decrypt_labs"` entries in the remote CDM list.
|
||
|
||
For example,
|
||
|
||
```yaml
|
||
decrypt_labs_api_key: "your_api_key_here"
|
||
```
|
||
|
||
**Note**: This is different from the per-CDM `secret` field in `remote_cdm` entries. This provides a global
|
||
API key that can be referenced across multiple DecryptLabs CDM configurations. If a `remote_cdm` entry with
|
||
`type: "decrypt_labs"` does not have a `secret` field specified, the global `decrypt_labs_api_key` will be
|
||
used as a fallback.
|
||
|
||
---
|
||
|
||
## decryption (str|dict)
|
||
|
||
Configure which decryption tool to use for DRM-protected content. Default: `shaka`.
|
||
|
||
Supported values:
|
||
- `shaka` - Shaka Packager (default)
|
||
- `mp4decrypt` - Bento4 mp4decrypt
|
||
|
||
You can specify a single decrypter for all services:
|
||
|
||
```yaml
|
||
decryption: shaka
|
||
```
|
||
|
||
Or configure per-service with a `DEFAULT` fallback:
|
||
|
||
```yaml
|
||
decryption:
|
||
DEFAULT: shaka
|
||
EXAMPLE: mp4decrypt
|
||
EXAMPLE2: shaka
|
||
```
|
||
|
||
Service keys are case-insensitive (normalized to uppercase internally).
|
||
|
||
---
|
||
|
||
## MonaLisa DRM
|
||
|
||
MonaLisa is a WASM-based DRM system that uses local key extraction and two-stage segment decryption.
|
||
Unlike Widevine and PlayReady, MonaLisa does not use a challenge/response flow with a license server.
|
||
Instead, the PSSH value (ticket) is provided directly by the service API, and keys are extracted
|
||
locally via a WASM module.
|
||
|
||
### Requirements
|
||
|
||
- **ML-Worker binary**: Must be available on your system `PATH` (discovered via `binaries.ML_Worker`).
|
||
This is the binary that performs stage-1 decryption.
|
||
|
||
### Decryption stages
|
||
|
||
1. **ML-Worker binary**: Removes MonaLisa encryption layer (bbts -> ents). The key is passed via command-line argument.
|
||
2. **AES-ECB decryption**: Final decryption with service-provided key.
|
||
|
||
MonaLisa uses per-segment decryption during download (not post-download like Widevine/PlayReady),
|
||
so segments are decrypted as they are downloaded.
|
||
|
||
**Note:** MonaLisa is configured per-service rather than through global config options. Services
|
||
that use MonaLisa handle ticket/key retrieval and CDM initialization internally.
|
||
|
||
---
|
||
|
||
## ClearKey DRM
|
||
|
||
Two distinct ClearKey mechanisms are supported; neither needs a CDM device or any DRM config:
|
||
|
||
### HLS AES-128 ClearKey
|
||
|
||
The key is fetched from (or near) the M3U8 `EXT-X-KEY` URI and segments are decrypted with
|
||
pure-Python AES-CBC. Fully automatic — nothing to configure.
|
||
|
||
### DASH ClearKey (`org.w3.clearkey`)
|
||
|
||
W3C EME ClearKey for DASH CENC content. The DASH parser recognises the clearkey
|
||
ContentProtection scheme (`urn:uuid:e2719d58-a985-b3c9-781a-b030af78d30e`), takes the KID from
|
||
`cenc:default_KID`, and reads the license server URL from the manifest's `<Laurl>` element when
|
||
present.
|
||
|
||
License flow: the W3C JSON license request (`{"kids": [...], "type": "temporary"}`) is POSTed to
|
||
the license server, which returns the content key as a JWK Set. Keys land in the same vault and
|
||
`--export` paths as Widevine/PlayReady, and decryption uses the same shaka-packager/mp4decrypt
|
||
CENC backends (`decryption` config option applies).
|
||
|
||
Service integration (simplest first):
|
||
1. Manifest carries a `<Laurl>` — works with zero service code.
|
||
2. Custom endpoint/headers — service overrides `get_clearkey_license`.
|
||
3. Bespoke key delivery — service pre-populates the DRM object's keys in `get_tracks`.
|
||
|
||
---
|
||
|
||
## key_vaults (list\[dict])
|
||
|
||
Key Vaults store your obtained Content Encryption Keys (CEKs) and Key IDs per-service.
|
||
|
||
This can help reduce unnecessary License calls even during the first download. This is because a Service may
|
||
provide the same Key ID and CEK for both Video and Audio, as well as for multiple resolutions or bitrates.
|
||
|
||
You can have as many Key Vaults as you would like. It's nice to share Key Vaults or use a unified Vault on
|
||
Teams as sharing CEKs immediately can help reduce License calls drastically.
|
||
|
||
Four types of Vaults are in the Core codebase: API, SQLite, MySQL, and HTTP. API and HTTP make HTTP requests to a RESTful API,
|
||
whereas SQLite and MySQL directly connect to an SQLite or MySQL Database.
|
||
|
||
Note: SQLite and MySQL vaults have to connect directly to the Host/IP. It cannot be in front of a PHP API or such.
|
||
Beware that some Hosting Providers do not let you access the MySQL server outside their intranet and may not be
|
||
accessible outside their hosting platform.
|
||
|
||
Additional behavior:
|
||
|
||
- `no_push` (bool): Optional per-vault flag. When `true`, the vault will not receive pushed keys (writes) but
|
||
will still be queried and can provide keys for lookups. Useful for read-only/backup vaults.
|
||
- `timeout` (float): Optional per-vault network timeout in seconds for the remote `API` and `HTTP` vaults.
|
||
Defaults to the global `vault_timeout` (10 seconds) so an unreachable vault host fails fast instead of
|
||
hanging the download. Set it on a vault to override the global, e.g. `timeout: 5.0`. Has no effect on
|
||
SQLite (local) or MySQL (uses its driver's own connect timeout).
|
||
|
||
Set a global default for all remote vaults at the top level of the config:
|
||
|
||
```yaml
|
||
vault_timeout: 10.0 # seconds; per-vault `timeout:` overrides this
|
||
```
|
||
|
||
### Using an API Vault
|
||
|
||
API vaults use a specific HTTP request format, therefore API or HTTP Key Vault APIs from other projects or services may
|
||
not work in unshackle. The API format can be seen in the [API Vault Code](unshackle/vaults/API.py).
|
||
|
||
```yaml
|
||
- type: API
|
||
name: "John#0001's Vault" # arbitrary vault name
|
||
uri: "https://key-vault.example.com" # api base uri (can also be an IP or IP:Port)
|
||
# uri: "127.0.0.1:80/key-vault"
|
||
# uri: "https://api.example.com/key-vault"
|
||
token: "random secret key" # authorization token
|
||
# no_push: true # optional; make this API vault read-only (lookups only)
|
||
# timeout: 5.0 # optional; per-vault network timeout (seconds), overrides vault_timeout
|
||
```
|
||
|
||
### Using a MySQL Vault
|
||
|
||
MySQL vaults can be either MySQL or MariaDB servers. I recommend MariaDB.
|
||
A MySQL Vault can be on a local or remote network, but I recommend SQLite for local Vaults.
|
||
|
||
```yaml
|
||
- type: MySQL
|
||
name: "John#0001's Vault" # arbitrary vault name
|
||
host: "127.0.0.1" # host/ip
|
||
# port: 3306 # port (defaults to 3306)
|
||
database: vault # database used for unshackle
|
||
username: jane11
|
||
password: Doe123
|
||
# no_push: false # optional; defaults to false
|
||
```
|
||
|
||
I recommend giving only a trustable user (or yourself) CREATE permission and then use unshackle to cache at least one CEK
|
||
per Service to have it create the tables. If you don't give any user permissions to create tables, you will need to
|
||
make tables yourself.
|
||
|
||
- Use a password on all user accounts.
|
||
- Never use the root account with unshackle (even if it's you).
|
||
- Do not give multiple users the same username and/or password.
|
||
- Only give users access to the database used for unshackle.
|
||
- You may give trusted users CREATE permission so unshackle can create tables if needed.
|
||
- Other uses should only be given SELECT and INSERT permissions.
|
||
|
||
### Using an SQLite Vault
|
||
|
||
SQLite Vaults are usually only used for locally stored vaults. This vault may be stored on a mounted Cloud storage
|
||
drive, but I recommend using SQLite exclusively as an offline-only vault. Effectively this is your backup vault in
|
||
case something happens to your MySQL Vault.
|
||
|
||
```yaml
|
||
- type: SQLite
|
||
name: "My Local Vault" # arbitrary vault name
|
||
path: "C:/Users/Jane11/Documents/unshackle/data/key_vault.db"
|
||
# no_push: true # optional; commonly true for local backup vaults
|
||
```
|
||
|
||
**Note**: You do not need to create the file at the specified path.
|
||
SQLite will create a new SQLite database at that path if one does not exist.
|
||
Try not to accidentally move the `db` file once created without reflecting the change in the config, or you will end
|
||
up with multiple databases.
|
||
|
||
If you work on a Team I recommend every team member having their own SQLite Vault even if you all use a MySQL vault
|
||
together.
|
||
|
||
### Using an HTTP Vault
|
||
|
||
HTTP Vaults provide flexible HTTP-based key storage with support for multiple API modes. This vault type
|
||
is useful for integrating with various third-party key vault APIs.
|
||
|
||
```yaml
|
||
- type: HTTP
|
||
name: "My HTTP Vault"
|
||
host: "https://vault-api.example.com"
|
||
api_key: "your_api_key" # or use 'password' field
|
||
api_mode: "json" # query, json, or decrypt_labs
|
||
# username: "user" # required for query mode only
|
||
# no_push: false # optional; defaults to false
|
||
# timeout: 5.0 # optional; per-vault network timeout (seconds), overrides vault_timeout
|
||
```
|
||
|
||
**Supported API Modes:**
|
||
|
||
- `query` - Uses GET requests with query parameters. Requires `username` field.
|
||
- `json` - Uses POST requests with JSON payloads. Token-based authentication.
|
||
- `decrypt_labs` - DecryptLabs API format. Read-only mode (`no_push` is forced to `true`).
|
||
|
||
**Example configurations:**
|
||
|
||
```yaml
|
||
# Query mode (requires username)
|
||
- type: HTTP
|
||
name: "Query Vault"
|
||
host: "https://api.example.com/keys"
|
||
username: "myuser"
|
||
password: "mypassword"
|
||
api_mode: "query"
|
||
|
||
# JSON mode
|
||
- type: HTTP
|
||
name: "JSON Vault"
|
||
host: "https://api.example.com/vault"
|
||
api_key: "secret_token"
|
||
api_mode: "json"
|
||
|
||
# DecryptLabs mode (read-only)
|
||
- type: HTTP
|
||
name: "DecryptLabs Cache"
|
||
host: "https://keyxtractor.decryptlabs.com/cache"
|
||
api_key: "your_decrypt_labs_api_key"
|
||
api_mode: "decrypt_labs"
|
||
```
|
||
|
||
**Note**: The `decrypt_labs` mode is always read-only and cannot receive pushed keys.
|
||
|
||
---
|