Kensio Software
Home Blog Discuss a Project
Home Expertise Technologies Blog Discuss a Project
  1. Home
  2. Blog
  3. @kensio/yulin v0.38.0 adds simulated KMS

@kensio/yulin v0.38.0 adds simulated KMS

@kensio/yulin package v0.38.0 adds simulated AWS KMS, so you can encrypt and decrypt with real key material and test key policies against simulated IAM in local development and isolated tests.

Hugh Grigg · Kensio Software · Tuesday 28 Jul 2026

  • AWS
  • TypeScript
  • Node.js

Version v0.38.0 of the @kensio/yulin package adds a simulated KMS service for local development and isolated testing.

The simulator supports symmetric encryption keys with CreateKeyCommand. It can encrypt and decrypt with EncryptCommand and DecryptCommand, including encryption context.

Envelope encryption is supported with GenerateDataKeyCommand, by KeySpec or NumberOfBytes.

Keys can be described and listed with DescribeKeyCommand and ListKeysCommand, and their policies read and replaced with GetKeyPolicyCommand and PutKeyPolicyCommand.

Aliases are created and listed with CreateAliasCommand and ListAliasesCommand, and the key lifecycle commands EnableKeyCommand, DisableKeyCommand, ScheduleKeyDeletionCommand and CancelKeyDeletionCommand are all supported.

The encryption is real encryption. Each simulated key holds real AES-256 key material and the operations run through Node.js’s own crypto, so a ciphertext genuinely cannot be read without its key, and attempting to decrypt with the wrong encryption context fails.

That runs fast enough in tests and local dev, and it adds a bit of extra realism to the simulation, which can give more confidence about how the code will behave against real KMS.

Creating a key and using it looks similar to real usage with the AWS SDK:

import {
  CreateKeyCommand,
  DecryptCommand,
  EncryptCommand,
} from "@aws-sdk/client-kms";
import { SimAws } from "@kensio/yulin";

const simAws = new SimAws();
const simKms = simAws.kms();

const createKeyOutput = await simKms.createKey(
  new CreateKeyCommand({ Description: "Example application key" }),
);

const encrypted = await simKms.encrypt(
  new EncryptCommand({
    KeyId: createKeyOutput.KeyMetadata?.Arn,
    Plaintext: Buffer.from("example-secret", "utf8"),
  }),
);

const decrypted = await simKms.decrypt(
  new DecryptCommand({ CiphertextBlob: encrypted.CiphertextBlob }),
);

console.log(Buffer.from(decrypted.Plaintext ?? []).toString("utf8"));

Decrypt does not need a KeyId for a symmetric key, because the ciphertext already names the key that produced it.

The ciphertext blob is opaque. It is not the plaintext, and it is not portable to real AWS or between two different SimAws instances. SimAws instances are always independent of each other and share no state.

An encryption context supplied to Encrypt is bound to that ciphertext, so decrypting with a different one throws SimKmsInvalidCiphertextException. The context is an unordered map, so the same pairs written in a different order still decrypt.

As in real AWS, Encrypt takes at most 4096 bytes, beyond which envelope encryption is necessary. GenerateDataKey returns a data key twice, once in the clear to encrypt your data with, and once encrypted under the KMS key to store alongside it.

Simulated KMS also simulates key policies in IAM, which is a source of a lot of surprises in real AWS projects. Being able to simulate that in Yulin helps to reduce the chance of those mistakes. As always, it’s still important to also test changes in a real AWS environment before going to production.

Every simulated KMS key has a policy which cannot be removed. An IAM policy granting kms:Decrypt does not allow the action unless the key’s own policy also admits the caller.

The way in which the key’s policy allows a caller determines what else is needed for authorisation. A statement naming the caller grants access outright, so a Role with no permissions of its own can still use the key.

A statement naming the Account root, which is what the default key policy contains, only delegates to that Account’s IAM service. The caller still needs an identity policy allowing the action.

Sim KMS evaluates all that through the same simulated IAM service used by the other simulated services.

import { CreateRoleCommand } from "@aws-sdk/client-iam";
import { CreateKeyCommand, EncryptCommand } from "@aws-sdk/client-kms";
import { SimAws } from "@kensio/yulin";
import { SimIamAccessDenied } from "@kensio/yulin/iam";

const simAws = new SimAws();
const accountId = simAws.defaultAccountId;

const createRoleOutput = await simAws.iam().createRole(
  new CreateRoleCommand({
    RoleName: "ExampleEncrypterRole",
    AssumeRolePolicyDocument: JSON.stringify({
      Version: "2012-10-17",
      Statement: {
        Effect: "Allow",
        Principal: { AWS: `arn:aws:iam::${accountId}:root` },
        Action: "sts:AssumeRole",
      },
    }),
  }),
);

const createKeyOutput = await simAws.kms().createKey(
  new CreateKeyCommand({
    Policy: JSON.stringify({
      Version: "2012-10-17",
      Statement: [
        {
          Effect: "Allow",
          Principal: { AWS: createRoleOutput.Role.Arn },
          Action: "kms:Encrypt",
          Resource: "*",
        },
      ],
    }),
  }),
);

await simAws.kms().encrypt(
  new EncryptCommand({
    KeyId: createKeyOutput.KeyMetadata?.Arn,
    Plaintext: Buffer.from("example-secret", "utf8"),
  }),
  { caller: { kind: "arn", arn: createRoleOutput.Role.Arn } },
);

try {
  await simAws.kms().encrypt(
    new EncryptCommand({
      KeyId: createKeyOutput.KeyMetadata?.Arn,
      Plaintext: Buffer.from("example-secret", "utf8"),
    }),
  );
} catch (error) {
  console.log(error instanceof SimIamAccessDenied);
}

That key policy names the Role directly and delegates nothing to the overall Account, so the Role can encrypt with no identity policy of its own, and the Account root cannot encrypt at all.

Replacing a key policy with PutKeyPolicyCommand can lock an Account out of its own key. That is real KMS behaviour, so the simulator allows for testing that out.

Every operation takes its target in the KeyId field, which can be any of the four forms accepted by real KMS: a key ID, a key ARN, an alias name such as alias/example-app-key, or an alias ARN.

Aliases beginning alias/aws/ are reserved for AWS managed keys, so CreateAlias refuses to create one. Referencing one brings the key into existence, the way an AWS managed key appears on real AWS when a service first needs it.

Keys belong to an Account and a Region, so a key created in one Region cannot be found or used from another, and a ciphertext produced under it cannot be decrypted elsewhere.

Deletion of a key is not immediate. ScheduleKeyDeletion sets a recovery window of 7 to 30 days during which use of the key is denied. It can still be recovered with CancelKeyDeletion, which leaves it disabled rather than enabled.

A disabled key fails cryptographic operations with DisabledException, and a key pending deletion fails with KMSInvalidStateException.

Function code requiring @aws-sdk/client-kms inside a simulated Lambda handler is routed into the same simulated AWS environment, with the function’s execution role as the caller. A handler that decrypts a value therefore has to be allowed by both the key policy and the role’s identity policy, the same as on real AWS.

AWS::KMS::Key and AWS::KMS::Alias are supported when deploying CloudFormation or CDK templates, so a key can be created from a template and an alias pointed at it, as well as through the SDK.

Sim KMS implements the behaviour that tests most commonly need rather than full KMS parity.

Only symmetric encryption keys are simulated, so asymmetric keys, HMAC keys, Sign, Verify and ReEncrypt are not. Grants, automatic key rotation, Multi-Region keys and imported key material are not simulated either.

kms:ViaService and kms:CallerAccount are derived. The other KMS-specific condition keys are not, including kms:EncryptionContext:*, so a policy relying on those will not match. Ordinary condition operators on values supported by sim IAM work as usual.

Other simulated services encrypt through KMS where real AWS does. Sim Secrets Manager encrypts every secret version, and sim SSM encrypts SecureString parameters.

Both use the AWS managed key for their service unless told otherwise, and an alias beginning alias/aws/ gets the equivalent policy that real AWS would give to such a key. Use of the key is allowed for the account’s principals only when kms:ViaService names the owning service.

That is why a Role holding only ssm:GetParameter reads a decrypted SecureString, while a Role holding kms:Decrypt on the same key cannot use it by calling KMS directly.

Sim S3, sim DynamoDB and sim Lambda environment variables do not use simulated keys yet, and do not check kms:Decrypt.

Key material lives in process memory for the lifetime of the SimAws instance. That is fine for a simulator, but it is not a security boundary, and anything sharing the process can reach it. The sim KMS source and tests show what is covered so far.

The KMS simulator integrates with the rest of Yulin, so IAM, Lambda, S3, CloudFormation and CDK can all participate in the same simulated environment for local development and system tests.

Related posts

  • Video: Automated documentation website updates on GitHub and AWS
  • What to include in a test object factory
  • Keep test state inside each test case
  • @kensio/yulin adds simulated Secrets Manager
  • @kensio/colophon social meta image npm package
  • @kensio/yulin v0.34.2 adds simulated Lambda

Latest posts

  • July 2026 @kensio/yulin v0.34.2 adds simulated IAM
  • July 2026 Video: Simulating AWS locally for website hosting with Yulin
  • July 2026 @kensio/yulin v0.24.0 adds simulated Route53
  • July 2026 @kensio/smartass assertions ESLint config rules
Kensio Software Home Blog Contact
Expertise MVP Development Web Development System Integrations APIs Database Development Cloud Software Serverless Systems E-Commerce
Technologies AWS TypeScript AWS Lambda Node.js Python Terraform PostgreSQL GCP Firebase MySQL
Elsewhere YouTube npm GitHub LinkedIn X

Kensio Software 2026