SSM (Parameter Store)
Lightweight, dependency-free fake of AWS Systems Manager (SSM) Parameter Store that speaks the real AWS JSON 1.1 wire protocol, so application code using @aws-sdk/client-ssm can run against it with zero cost and zero side effects.
| Key | Value |
|---|---|
| Port | 4578 |
| Protocol | AWS JSON 1.1 (X-Amz-Target: AmazonSSM.<Operation>) over HTTP |
| Compatible client | @aws-sdk/client-ssm (v3) |
| Size | ~60 KB |
| Startup | < 100ms |
| State | In-memory, ephemeral, resettable |
Quick Start
Start the server:
import { SsmServer } from "./services/ssm/src/server.js";
const server = new SsmServer(4578);
await server.start();
// ... use it ...
await server.stop();
Connect with the real AWS SDK client:
import {
SSMClient,
PutParameterCommand,
GetParameterCommand,
GetParametersByPathCommand,
} from "@aws-sdk/client-ssm";
const ssm = new SSMClient({
region: "us-east-1",
endpoint: "http://127.0.0.1:4578",
credentials: { accessKeyId: "parlel", secretAccessKey: "parlel" },
});
// Write a parameter
await ssm.send(
new PutParameterCommand({ Name: "/app/db/host", Value: "localhost", Type: "String" }),
);
// Write a secret
await ssm.send(
new PutParameterCommand({ Name: "/app/db/password", Value: "s3cr3t", Type: "SecureString" }),
);
// Read it back
const { Parameter } = await ssm.send(new GetParameterCommand({ Name: "/app/db/host" }));
console.log(Parameter.Value); // "localhost"
// Fetch a whole hierarchy
const tree = await ssm.send(
new GetParametersByPathCommand({ Path: "/app", Recursive: true }),
);
console.log(tree.Parameters.map((p) => p.Name));
Configuration via environment variables
The @aws-sdk/client-ssm client honors these, set them to point the SDK at the fake:
AWS_ACCESS_KEY_ID=parlel
AWS_SECRET_ACCESS_KEY=parlel
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_SSM=http://127.0.0.1:4578
AWS_ENDPOINT_URL=http://127.0.0.1:4578
Implemented operations
Parameter CRUD
PutParameter— create/overwrite; versions auto-increment; supportsString,StringList,SecureString;Tier(Standard/Advanced/Intelligent-Tiering);AllowedPattern,Description,DataType,KeyId,Tags(on create only).GetParameter— by name, byname:version, orname:labelselector;WithDecryptionaccepted (no-op).GetParameters— batch (max 10); returnsParameters+InvalidParameters.GetParametersByPath— hierarchical fetch,Recursive,ParameterFilters, pagination.DeleteParameterDeleteParameters— batch; returnsDeletedParameters+InvalidParameters.DescribeParameters— metadata only (no values), legacyFilters+ modernParameterFilters, pagination.GetParameterHistory— all versions with labels, in order, pagination.
Version labels
LabelParameterVersion— attach labels to a version (labels auto-move between versions); validates label rules; returnsInvalidLabels.UnlabelParameterVersion— detach labels; returnsRemovedLabels+InvalidLabels.
Tagging
AddTagsToResourceRemoveTagsFromResourceListTagsForResource
Resource policies
PutResourcePolicyGetResourcePoliciesDeleteResourcePolicy
Service settings
GetServiceSettingUpdateServiceSettingResetServiceSetting
Parlel internal (not part of SSM)
GET /_parlel/health—{ status, service, parameters }POST /_parlel/reset— clears all in-memory state
Surface coverage
This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.
Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.
| Feature | Status |
|---|---|
Parameter Store CRUD (PutParameter, GetParameter(s), GetParametersByPath, Delete..., Describe..., GetParameterHistory) | ✅ Supported |
Parameter versioning + version selectors (name:version) | ✅ Supported |
Version labels + label selectors (name:label) | ✅ Supported |
Parameter types String / StringList / SecureString | ✅ Supported |
Tiers Standard / Advanced / Intelligent-Tiering (size-based promotion) | ✅ Supported |
AllowedPattern validation | ✅ Supported |
Hierarchies / recursive path queries / ParameterFilters | ✅ Supported |
Tagging (AddTagsToResource, RemoveTagsFromResource, ListTagsForResource) | ✅ Supported |
Resource policies (PutResourcePolicy, GetResourcePolicies, DeleteResourcePolicy) | ✅ Supported |
Service settings (Get/Update/Reset ServiceSetting) | ✅ Supported |
Pagination (MaxResults / NextToken) | ✅ Supported |
Real KMS encryption of SecureString | ✓ By design — Always succeeds deterministically — no real funds move |
Policies (expiration / no-change notifications) actually firing | ⟳ Roadmap — Stored + echoed in metadata, but never executed |
Automation, Documents (SSM Docs), Run Command (SendCommand), Sessions | ⟳ Roadmap — fleet/operations, not Parameter Store |
| Maintenance Windows, Patch Baselines, Inventory, OpsItems, Associations | ⟳ Roadmap |
Cross-account Shared parameters | ⟳ Roadmap |
Error codes / shapes
Errors are returned with a non-2xx HTTP status and an AWS JSON 1.1 body:
{ "__type": "ParameterNotFound", "message": "Systems Manager could not find the parameter /missing." }
The x-amzn-errortype response header carries the same code. The @aws-sdk/client-ssm
client maps __type to error.name.
| Code | HTTP | When |
|---|---|---|
ParameterNotFound | 400 | The named parameter (or label) does not exist |
ParameterAlreadyExists | 400 | PutParameter on an existing name without Overwrite: true |
ParameterVersionNotFound | 400 | A name:version selector points at a non-existent version |
ParameterPatternMismatchException | 400 | Value fails AllowedPattern |
InvalidAllowedPatternException | 400 | AllowedPattern is not a valid regular expression |
ValidationException | 400 | Missing/invalid input (no Type on create, bad Type, reserved name prefix, oversized value, empty Names, bad Path, etc.) |
InvalidResourceId | 400 | Tagging a parameter that does not exist |
InvalidFilterKey | 400 | Unknown filter key for Describe/GetParametersByPath |
InvalidNextToken | 400 | Malformed pagination token |
MalformedResourcePolicyDocumentException | 400 | PutResourcePolicy with non-JSON policy |
ResourcePolicyNotFoundException | 400 | DeleteResourcePolicy for an unknown PolicyId |
ResourcePolicyConflictException | 400 | PutResourcePolicy update with a stale PolicyHash |
InternalFailure | 500 | Unexpected server error |
Notes on fidelity
- Timestamps are serialized as epoch-second numbers, matching the AWS JSON 1.1 wire format the SDK expects.
- Parameter names may be flat (
my-param) or hierarchical (/a/b/c). Names beginning with the reservedaws/ssmprefixes are rejected, matching AWS. SecureStringvalues are stored as-is (no KMS). This keeps the fake dependency-free;WithDecryption: truesimply returns the stored value.- Standard tier caps values at 4096 bytes; Advanced at 8192 bytes.
Intelligent-Tieringis promoted toAdvancedwhen the value exceeds the standard limit orPoliciesare present.
Configuration — test.env
Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.
AWS_ACCESS_KEY_ID=parlel
AWS_SECRET_ACCESS_KEY=parlel
AWS_REGION=us-east-1
AWS_ENDPOINT_URL_SSM=http://parlel-bridge:4578
AWS_ENDPOINT_URL=http://parlel-bridge:4578
<!-- parlel:testenv:end -->