Damage nsecbunker: Custody, Signing Policy and Live Verification

Let a client request a signature under a policy

A Nostr client may need to sign an event without holding the signing key itself. A remote signer can provide that separation, but it also needs rules for which clients, methods and event kinds are allowed.

damage_nsecbunker is DamageBDD's NIP-46 signing service. It places those policy checks between a request and the configured vault/cryptography backend. The relay adapter carries requests and responses; the bunker decides whether an operation is allowed and invokes the signing path.

For example, an operator can permit a client to request an approved article signature while refusing other methods or event kinds. This creates a smaller permission to reason about than handing the client unrestricted signing material. The practical result still depends on the configured policy, vault, host and client behaviour.

Release-build run 20260925132037 on 25 September 2026 records the acceptance scenarios described below against an installed package. The report provides a concrete operational example; the repository links explain the source interfaces separately from that historical result.

Service and transport boundaries

The server reads configuration from application:get_env(damage, nsecbunker) through its configuration module. It builds a policy, prepares the runtime vault and reports whether it is ready. Bootstrap failures leave it unready and subject to its retry path. A ready status is necessary before requests can be accepted.

Inbound relay traffic passes through damage_nsecbunker_relay and damage_nostr_relay_client to the bunker. The bunker produces a response event; the relay path publishes it. The adapter handles delivery, while policy and cryptographic operations remain in the bunker path.

The live test encrypts its requests through the NIP-44 client helper and validates decrypted responses for the current request. NIP-46 provides the remote-signing message contract; see the upstream specification. The successful restricted-policy suite is not a claim that every optional NIP-46 method is enabled or tested.

What policy controls

The service builds policy from configured client keys, allowed methods, allowed event kinds, timestamp tolerance, event-size limits, required tags and content restrictions. It also carries signing timeout and rate-limit settings. Defaults and effective values depend on the installed configuration and policy module; the live report should be read against that configuration.

For the recorded deployment, the suite exercises ping, get_public_key and allowed kind-30023 signing. It checks rejection of a non-allowlisted method and a kind outside the running policy. Returning a signed article and publishing that article are separate actions. Response events must still travel back to the client even when article publication by the bunker is disabled.

The non-publication assertion is a bounded observation on the checked relay path. It should not be interpreted as proof that no copy of an event could exist anywhere or be published by another authorised holder later.

AWS custody evidence

The release run explicitly checks that aws_secrets_manager is the running secret provider and that the local DETS nsecbunker_vault_passphrase lookup has no entry. Vault readiness and agreement between the vault guard public key and running policy also pass.

This establishes the tested runtime state. It does not independently document the historical deletion of a DETS entry, a subsequent restart with that entry absent, the full IAM policy or the security of every host process. AWS secret retrieval is also not evidence of non-exportable HSM signing.

An operator should distinguish setup permissions from ongoing runtime permissions. The repository's AWS bootstrap guide describes the configuration and identity checks for that deployment path. Keep access revocation, rotation and recovery records alongside the acceptance report; the report alone does not establish that those administrative actions occurred.

Inspect the active service

The repository exports damage_nsecbunker:status/0 for service status. It also handles an internal handoff_status request that returns readiness, start time, secret provider, bunker public key and the authorised client list from the running server state.

At the linked revision, there is no exported handoff_status/0 wrapper. An administrator using the local Erlang shell can inspect the handler with an explicit call:

damage_nsecbunker:status().
Handoff = gen_server:call(damage_nsecbunker, handoff_status, 5000).
maps:get(ready, Handoff).
maps:get(secret_provider, Handoff).
maps:get(bunker_pubkey_hex, Handoff).
maps:get(authorized_clients, Handoff).

These are local administrative operations, not public HTTP endpoints. The ordinary status contains a policy summary; an omitted client list there does not imply an empty allowlist. policy/0 derives policy from configuration, while the handoff handler reports selected fields from active server state.

After an authorised configuration change, reload/0 validates and prepares a candidate runtime. Failure returns an error and retains the previous state; an unready server rejects reload through its not-ready branch. Inspect the returned result and active state before treating the change as applied.

What the twelve scenarios cover

Scenario Recorded outcome
Identity and subscription-filter consistency Passed
AWS-only custody and post-rotation handoff assertions Passed
Separate-connection relay ingress canary Passed
Black-box full relay ping loop Passed
Black-box allowed article signing loop Passed
Local core ping with the node client Passed
Local public-key retrieval Passed
Live relay ping/pong Passed
Live relay public-key retrieval Passed
Non-allowlisted method rejection Passed
Disallowed event-kind rejection Passed
Allowed article signing with non-publication check Passed

The suite also asserts audit correlation and the absence of secret material in its test context. Those checks do not establish that every application log, OS dump or unrelated storage location is secret-free.

Relay scenarios use the node's existing damage_nostr identity. The designated external client key is checked separately through the local policy gate. That confirms the configured permission, but does not exercise the external client's own key, connection and reply handling. Run that client's smoke test as a separate handoff check.

Exact release evidence

Field Recorded value
Run ID 20260925132037
Result ok / success
Release 1.7.5-rc2+build.1603.ref28a4047
Git SHA 28a4047ece90d15f1ba94ece4a6b0fcbbf57b808
Installation origin package
Feature CID QmZvjmonj42YtL7hDHi64BWWG5DKriYLpA7svzmnrWrMGY

Runtime code hash:

52eb0f56f8d8b62c61ba789fe361f1938a48f274a9592c66fd16eb92ec84f470

These identifiers belong to the recorded September run. No new execution was performed for this article. Its transaction reference is th_FMRtRhp8hRDCgVsUmb3mc6n4vniEdvqFCXB3N37dbZPeEXGJ9.

Complete the operating handoff

A working request/signature loop is one part of running a signing service. The operator also needs a tested key and secret rotation procedure, a vault backup and restore procedure, and evidence that temporary setup access has been removed. Each should have a recorded result for the deployed environment.

For the AWS path, an AWSPENDING-to-AWSCURRENT rotation rehearsal checks how the service moves between secret versions. A restore exercise checks whether the operator can recover the required vault state. Neither follows from a successful signing request alone.

Finish with the intended external client's end-to-end smoke test. Preserve the configuration revision, public identities, request outcome and relevant audit record. These give the next operator enough context to repeat the handoff after an upgrade or recovery.

Explore the implementation and operating guides