Your KMS key. Your audit trail. Our blobs go dark when you say so.

Every server-stored ciphertext blob (Vault, inheritance contracts) wrapped under a per-record AES-256-GCM data key, itself encrypted under your AWS KMS CMK. Four steps from console to active.

Four steps

  1. Create a symmetric CMK in AWS KMS
  2. Create an IAM role with our trust policy
  3. Paste both ARNs into the deny.sh BYOK dashboard
  4. Verify, activate, and watch your CloudTrail

Step 1 of 4

Create a symmetric CMK in AWS KMS.

In the AWS console, go to KMS → Customer managed keys in the region you want to use, click Create key, and pick:

Key type

Symmetric

Key usage

Encrypt and decrypt

Origin

KMS (default). External import + CloudHSM also work.

Multi-region

Off (single region). Multi-region keys work too but add complexity.

Give it a descriptive alias such as alias/deny-sh-byok and add a tag deny-sh:purpose = byok so it shows up in cost-allocation reports separately. Skip the Key administrators step (your normal admins are fine). On the Define key usage permissions step, leave the IAM role list empty for now. We'll add the deny.sh role in step 2.

After creation, copy the ARN (looks like arn:aws:kms:us-east-1:123456789012:key/uuid). You'll paste it into the BYOK dashboard in step 3.

What deny.sh ever calls on this key: kms:GenerateDataKey (when wrapping a new blob) and kms:Decrypt (when reading one back). Nothing else.


Step 2 of 4

Create an IAM role with our trust policy.

deny.sh assumes a role in your AWS account on every wrap and every unwrap. That role's trust policy names our AWS account as the only principal allowed to assume it.

In IAM → Roles → Create role, pick Custom trust policy and paste:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowDenyShAssumeRole",
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::<DENY_SH_AWS_ACCOUNT_ID>:root"
    },
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": {
        "sts:ExternalId": "<YOUR_TENANT_ID>"
      }
    }
  }]
}

The DENY_SH_AWS_ACCOUNT_ID is shown in your BYOK dashboard (the ID is public and stable; it doesn't change per tenant). The YOUR_TENANT_ID is your deny.sh tenant ID, also shown in the dashboard. The sts:ExternalId condition prevents the confused-deputy problem and is non-optional in our wire format.

Then attach one inline policy with these permissions, scoped to your CMK ARN:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyShEnvelopeOps",
    "Effect": "Allow",
    "Action": [
      "kms:GenerateDataKey",
      "kms:Decrypt"
    ],
    "Resource": "<YOUR_CMK_ARN>"
  }]
}

Give the role a name like deny-sh-byok-role and copy its ARN (looks like arn:aws:iam::123:role/deny-sh-byok-role).

Finally, update the CMK's key policy to grant this role the same two actions. KMS resource policies and IAM policies are AND-ed, so both sides have to agree. In KMS → your key → Key policy, switch to JSON view and add a statement:

{
  "Sid": "AllowDenyShRoleToUseKey",
  "Effect": "Allow",
  "Principal": {
    "AWS": "<YOUR_ROLE_ARN>"
  },
  "Action": [
    "kms:GenerateDataKey",
    "kms:Decrypt",
    "kms:DescribeKey"
  ],
  "Resource": "*"
}

Step 3 of 4

Paste both ARNs into the deny.sh BYOK dashboard.

Open /dashboard/byok while signed in with your consumer session. Paste the CMK ARN, the IAM role ARN, and pick the region. Click Register.

State transitions:

On register

PENDING_VERIFICATION. We try a roundtrip immediately.

On successful verify

ACTIVE. Future writes envelope-wrap. Future reads unwrap.

On verify failure

FAILED. Error code shown (role denied / key not found / region mismatch / permission missing). No state change to existing blobs.

On revoke

REVOKED. Future writes go through unwrapped. Reads of historical wrapped rows return BYOK_UNAVAILABLE until re-registered.

The verify call exercises the full path: STS AssumeRole, KMS GenerateDataKey, KMS Decrypt, comparison. If anything is misconfigured, you get a precise error code back. No partial state writes.


Step 4 of 4

Verify, activate, and watch your CloudTrail.

Once active, every deny.sh wrap or unwrap on your data shows up in your AWS CloudTrail as a GenerateDataKey or Decrypt event under your CMK, with our IAM role as the principal, and our session name as deny-sh-byok-<tenant-prefix>.

You also get our side of the story in /dashboard/audit: every envelope event lands in the hash-chained audit log with op type byok.envelope.created or byok.envelope.unwrapped, plus byok.config.created, byok.config.verified, byok.config.rotated, byok.config.revoked, and byok.kms.failure for any error. Cross-reference the two trails any time.

To revoke us, click Revoke in the BYOK dashboard, or just remove the IAM role's trust policy from your side. Both work. From that moment, we cannot unwrap anything we previously wrapped under your key. The blobs go dark. Re-register the same CMK ARN to un-dark them.

To rotate: create a fresh CMK, swap its ARN in the BYOK dashboard. New writes use the new CMK; old wrapped blobs remain readable under the old CMK as long as the old CMK still exists and the IAM role still has access. (V1 leaves old blobs under the old DEK; we do not bulk-re-envelope on rotate. Roll the new key forward; the old one stays as long as it stays.)

Ready to wire it up?

BYOK is included in agents-infra, enterprise, and inherit-institutional. No extra SKU. Open the dashboard, paste your ARNs, watch CloudTrail.

Open BYOK dashboard Read the long version