Skip to main content

View source on GitHub

A Python script you run on your own scheduler. It reads explicitly mapped AWS Secrets Manager values, writes them through OpenHands secrets APIs, and updates them when AWS rotates them. AWS is the working example: you can adapt the source reader to another secrets manager. Nothing is installed in OpenHands.

How It Works

The script creates missing entries, skips unchanged AWS versions, and updates changed versions only for mappings already owned by its journal. Unknown existing names or edited ARN/field mappings are refused; use a new destination name or operator recovery. Source deletion and removed mappings never cause destination deletion.

Prerequisites

  • Python 3.10+ on Linux or macOS; boto3 and requests from requirements.txt.
  • AWS credentials through the Boto3 credential chain. Grant DescribeSecret and GetSecretValue on the mapped ARNs; see the IAM example and AWS access documentation. Customer-managed encryption keys may require kms:Decrypt.
  • An organization-bound OpenHands API key injected as OH_API_KEY. Shared-secret writes require owner/admin permission. Keep credentials out of mappings and command arguments. GET /api/keys/current returns organization and user IDs; verify they match the intended account and organization before configuring them.

Run It

From this directory, install dependencies:
Set the mapping’s HTTPS origin, organization UUID, exact AWS ARN, and destination name (a valid environment-variable name). json_field selects one top-level string field; omit it for the whole string. Binary values are unsupported.
Copy mapping.example.json to mapping.local.json and edit it. Use the same journal for preview, initial import, and recurring sync.
After the source version changes, run:
This command deletes and re-creates shared values, including on newer servers. If creation fails, the secret can remain missing for all members until recovery. Read Updates and Recovery before scheduling it.
Omit --apply to preview without reading values. Unchanged versions print:
Your scheduler can run the recurring command weekly with absolute paths, injected credentials, and the same persistent journal. Its directory must be private (chmod 700). Run one exclusive writer per destination and alert on any nonzero exit; successful mappings remain committed when another fails.

Secret Name Collisions

On the tested API, personal secrets belong to a user within the effective organization. Personal and shared names are separate namespaces; neither create endpoint rejects a name used in the other scope. The personal value keeps the bare name; the shared value receives a suffix (_2, _3, …) in that user’s conversations and GET /api/v1/secrets/search. The stored shared name remains unchanged in the organization listing.
An organization sync cannot see members’ personal secrets. A member with a matching personal name keeps their own value under that name; rotating the shared value only changes their suffixed variable, such as NAME_2.
Personal preflight rejects mapped names that match shared names as listed in the search results. A shared secret created later can appear already suffixed, so the next sync does not detect that base-name collision. Use team/system prefixes and reserve managed names across scopes; this script cannot enforce that policy.

Updates and Recovery

Personal values use upsert. This recipe rotates shared values with DELETE then POST, only with --sync --allow-delete-recreate. It validates the new AWS value before deleting anything. The existing organization metadata PUT cannot replace values; the newer value-replacement API is separate (see Compatibility).
After DELETE succeeds, the shared entry is absent until POST succeeds. New conversations started in that window lack the shared value. A failed or unacknowledged POST can leave it missing for all members until an operator completes recovery; it is not just a brief availability gap. A timeout can also hide a successful write. The script blocks uncertain mappings instead of guessing or automatically retrying them.
New conversations receive updated values; running conversations can retain old values. Credential revocation is a separate process. Keep exclusive control of managed names: the operations used here have no conditional-write contract, and the local journal lock cannot coordinate other clients. Out-of-band destination value changes are not detected. A deleted checkpoint also blocks if the entry is unexpectedly present; reconcile it before proceeding. Do not erase the journal to bypass uncertainty. Values and tokens are not intentionally logged or stored in it; disable HTTP/SDK wire logging in your runner.

Adapt to Another Vault

In sync_secrets.py, replace current_version() and read_value() with your vault’s metadata/version and value API calls. Adjust the client factory, ARN-to-region parsing in sync(), mapping validation, and source error handling. Keep a stable source ID and version token; read the exact requested version and fail safely if it changes between metadata and value reads. Keep the OpenHands adapters and journal for the destination side. Other vaults are extension points, not included integrations; qualify their authentication, versioning, retries, and synthetic-secret delivery before use.

APIs Used

Compatibility

The maintainer review validated this script at 2e83432 on Enterprise 1.74.0, using simulated AWS (moto) and real OpenHands APIs and agent consumption of rotated values. Qualify your deployment before importing real credentials; this is not a minimum-version or blanket compatibility guarantee. Enterprise 1.81.0 added atomic shared-value replacement through separate GET .../secrets/{name}/version and PUT .../secrets/{name}/value endpoints. This recipe does not use or qualify them and still deletes/re-creates shared values on newer servers. Adopting that API requires a separately tested adapter and conditional-version/retry handling; the ordinary metadata PUT remains metadata-only. Inspect your deployment’s /openapi.json before choosing a path.

OpenHands Secrets Management

Managing and using secrets

AWS GetSecretValue

Exact version reads

Per-Conversation Secrets

A separate delivery scope