Intelligence

The Data Model Named a Table. The Generated Stack Executed What It Said.

CVE-2026-85654, published 4 September 2026, describes the CDK generator inside Amazon's awslabs.dynamodb-mcp-server turning table, index and attribute names from a dynamodb_data_model.json file into a generated AWS CDK application without validating or escaping them, from the generator's own introduction in version 2.0.10 through version 2.1.5. This record independently cloned awslabs/mcp and read the affected stack.ts.j2 template and the exact fix, commit 46ca139f, confirming the mechanism directly: table and index names reached TypeScript identifier positions with no character check, and every attribute, key and time to live name reached single quoted TypeScript string literal positions with no escaping. A crafted name could break out of that literal or corrupt the identifier, and the resulting TypeScript source only takes effect later, when a person or a pipeline compiles and deploys the generated CDK application, so whatever a crafted name made that source say runs with that later deployer's own host and cloud authority, not any authority the data model's own author held.

Event analysed: . This analysis was published on 6 September 2026.

If a DynamoDB data model file only ever holds names, table names, index names, attribute names, how could a crafted one execute arbitrary code, and at what point does that code actually run?

The names never execute anything by themselves. The CDK generator inside awslabs.dynamodb-mcp-server turns them into a TypeScript source file, and before version 2.1.6, that generation step neither validated names destined for TypeScript identifier positions nor escaped names destined for TypeScript string literal positions. This record independently confirmed both gaps by reading the affected stack.ts.j2 template directly. A crafted attribute name containing a single quote could close a string literal early and place further TypeScript statements after it; a crafted table or index name reached an identifier position where no escaping mechanism could apply at all. Either way, nothing in the generation step itself runs that injected code. The generated stack.ts file is an ordinary, persisted source file, and the code it carries only takes effect later, when a person or a pipeline compiles and deploys the generated CDK application, typically with cdk deploy. GitHub Security Advisory GHSA-hh4r-pcm9-jh93 formally assigns CVE-2026-85654 on 4 September 2026, CVSS 4.0 base score 7.1, High, CWE-1336, stating the consequence precisely as executing arbitrary code on the host that deploys the generated application, not on the host that generated it. This record independently confirmed the mechanism by cloning awslabs/mcp and reading the exact fix commit, 46ca139f, which validates table and index names against DynamoDB's own permitted character set before they reach an identifier position, and wraps every attribute, key and time to live name in Jinja's tojson filter before it reaches a string literal position. That fix shipped in version 2.1.6, confirmed independently as a direct ancestor of the commit that bumps the package's own version from 2.1.5 to 2.1.6, and this record confirmed directly that the current default branch, version 2.1.7, still carries it unchanged.

Read the generator's own template directly and the shape of the problem is exactly as narrow as it sounds and exactly as consequential as the advisory states. A dynamodb_data_model.json file holds nothing but names: a table name, an index name, an attribute name, a partition key. None of those values is code, and nothing in the file format asks for any. The CDK generator's own job is to turn that data into a working AWS CDK application, and turning data into a programming language's own source text is precisely the step where a value's role can change without anyone deciding it should.

What the advisory states

GitHub Security Advisory GHSA-hh4r-pcm9-jh93 formally assigns CVE-2026-85654 on 4 September 2026, CVSS 4.0 base score 7.1, High, vector CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N, and classifies it CWE-1336, Improper Neutralization of Special Elements Used in a Template Engine. Its own description states that improper neutralization of special elements used in a template engine in the CDK generator in Amazon awslabs.dynamodb-mcp-server before version 2.1.6 might allow a context dependent actor to execute arbitrary code on the host that deploys the generated application, via crafted table, index or attribute names in a data model file. The advisory itself carries no structured, machine readable affected version range for a supported package ecosystem, a gap this record states rather than works around: GitHub's own Dependabot alert tooling is not supported on this advisory for exactly that reason, confirmed directly on the advisory's own page. The advisory's own reference list names an AWS Security Bulletin, 2026-097-AWS. This record could not reach aws.amazon.com directly, since this session's own network egress policy blocked it on every attempt, so what follows about that bulletin rests on the advisory's own reference listing rather than a direct reading of the bulletin's own text, and this record does not adopt a specific time of day for its publication beyond the shared 4 September 2026 date the advisory itself carries.

What this record independently confirmed by cloning awslabs/mcp directly and reading the real, unmodified source, rather than by reading a description of it: the affected template, stack.ts.j2, interpolated a table's partition key name, sort key name, every global secondary index key name, its non key attributes and its time to live attribute as bare single quoted TypeScript string literals, for example name: '{{ table.partition_key.name }}', with no escaping. The same template separately interpolated table and index names, unescaped, into TypeScript identifier positions built by the generator's own to_camel_case and to_pascal_case filters, a position no quoting mechanism can neutralize at all.

Two positions, two different failures

DynamoDB itself treats table and index names differently from every other name in the same data model. Table names and index names are restricted to a fixed character set, letters, digits, underscore, dot and hyphen, three to two hundred fifty five characters, confirmed directly against the fix commit's own comment citing the DynamoDB API's CreateTable reference. Attribute names, including a partition key, a sort key, a global secondary index key, a non key attribute and a time to live attribute, carry no character restriction in the DynamoDB API at all beyond length.

The generator's own template treats the two groups identically before the fix, and that uniform treatment is exactly what the fix corrects, in two different directions rather than one. A table or index name is used to build a TypeScript variable or method name, through the generator's own to_camel_case and to_pascal_case filters, confirmed directly by reading generator.py: the pre fix version split the name on hyphens and underscores, capitalized words, and returned the result with no check that the result was a legal identifier at all, no rejection of a leading digit, no rejection of a name made entirely of separator characters. An identifier position cannot be escaped; whatever character sequence reaches it either is a legal piece of a TypeScript variable name or it corrupts the generated source. An attribute or key name, by contrast, reaches a string literal position, name: '{{ ... }}', where a single quote inside the value closes the literal early and TypeScript reads whatever follows as further source code, up to the next syntactic boundary the generator's own template happens to supply.

The fix, read directly against the affected commit

This record read the complete diff of commit 46ca139f27020e1b54b549d136138c6dc3ac84e2, titled fix, dynamodb-mcp-server, validate and escape names in CDK generator, merged as pull request 4384, directly from the cloned repository rather than from a page describing it. The fix adds a DDB_NAME_PATTERN regular expression, letters, digits, underscore, dot and hyphen only, applied with Python's fullmatch rather than match, since match alone would let a trailing newline through a pattern anchored only with a dollar sign, a distinction the commit's own second change explicitly calls out and corrects. Every table name and index name is now validated against that pattern and a three to two hundred fifty five character length bound before it ever reaches to_camel_case or to_pascal_case, so a name that cannot form a legal identifier is rejected at parse time rather than silently corrupted or exploited at generation time. Every partition key, sort key, global secondary index key, non key attribute and time to live attribute name is now validated only for length, since DynamoDB itself imposes no character restriction on them, and every one of their interpolations in stack.ts.j2 is now wrapped in Jinja's tojson filter, the same filter the template already applied to non key attributes before this fix, so a value containing a quote, a brace or any other TypeScript special character is emitted as a properly escaped JSON string literal rather than a bare one. The same commit also hardens to_camel_case directly: a dot is now treated as a word separator alongside hyphen and underscore, a name made entirely of separator characters falls back to a fixed prefix rather than producing an empty identifier, and a name that would otherwise start with a digit is prefixed the same way, with the commit's own follow up change additionally making the generator report a collision when that prefix produces two methods with the same name rather than silently emitting both.

Where the vulnerable code came from, and where the fix landed, read from history rather than from the advisory

This record traced the generator's own history directly in the cloned repository instead of accepting the advisory's own unstated lower bound. The CDK generator, and the exact unescaped template this record describes above, was introduced by commit 15e43c67, feat, dynamodb_mcp_server, add CDK app for table creation, merged 14 January 2026. At that commit, the package's own pyproject.toml still read version 2.0.9. The very next commit touching that file, an automated release commit, bumps it to 2.0.10, independently confirming the version the CVE names as the lower bound of the affected range is the version that first shipped the vulnerable generator, rather than an estimate this record simply repeated. The fix, commit 46ca139f, dated 13 August 2026, is a direct ancestor of commit eff6cce0, an automated release commit titled chore, bump packages for release, that changes the package's own pyproject.toml from 2.1.5 to 2.1.6, confirmed by this record through a direct ancestry check in the cloned repository rather than assumed from adjacent dates. That commit is the release revision, not the remediation commit itself, and this record states that distinction directly: the fix landed one day earlier, and the release commit's own purpose is only to carry the version number forward once the fix it descends from has already merged. This record separately read the current default branch, where the package's own pyproject.toml reads version 2.1.7, and confirmed the same DDB_NAME_PATTERN validation and the same tojson escaping remain present, unchanged, in the current CDK generator.

Why this is a deployment time consequence, not a generation time one

Nothing in the CDK generator's own generation step executes the TypeScript it writes. Generation produces a persisted CDK application, an ordinary directory of source files a caller can inspect, commit, hold, or discard, and the generator's own process ends once that directory exists. Whatever a crafted name changed about the generated stack.ts file takes effect only when something later compiles and deploys that application, typically a person or an automation pipeline running cdk deploy, an action that can happen on a different host, under a different identity, and at a materially later time than the one at which the data model was authored or the generation tool was invoked. The advisory's own stated consequence, arbitrary code execution on the host that deploys the generated application, names exactly that later moment, and this record's own reading of the affected and fixed source supports it precisely: the mechanism this record confirmed operates entirely inside a template rendering step, with no code path in the generator itself that compiles or executes the file it writes.

The Authority Provenance ledger

Source principal. Whoever authored or supplied the dynamodb_data_model.json file the generator reads, a role that in an agentic workflow could be an LLM agent itself, confirmed by this record as needing no AWS deployment authority of any kind to place a crafted name into that file.

Generation action. Amazon's own dynamodb-mcp-server CDK generator tool, invoked against that data model file, confirmed directly by this record's own reading of generator.py and models.py as the exact code path that parses the file and renders stack.ts.j2.

Generated artifact. A persisted stack.ts file inside an ordinary CDK application directory, confirmed by this record as outliving the generation call itself, with no code path in the generator that reads it back or executes it.

Deployment principal. Whoever later runs cdk deploy, or an equivalent build and deploy step, against the generated application, a principal this record's own reading of the generator finds no requirement to be the same actor, session, or even organization that authored the data model or invoked generation.

Effect before the fix. Arbitrary TypeScript source injected through a crafted attribute, key or time to live name breaking out of an unescaped string literal, or through a crafted table or index name corrupting an unvalidated identifier position, confirmed directly by this record's own reading of the pre fix stack.ts.j2 and generator.py.

Failed limit. Two, confirmed directly, matching the two positions DynamoDB's own naming rules create. The identifier position had no validation at all, and an identifier position cannot be escaped once a value is destined for one. The string literal position had no escaping at all, for every one of five distinct interpolation sites this record read directly in the template.

Recovery path. Character set and length validation for table and index names before they reach an identifier position, and tojson escaping for every attribute, key and time to live name at every string literal interpolation, confirmed directly by this record's own reading of commit 46ca139f.

Recovery timing. Merged 13 August 2026 and confirmed by this record as a direct ancestor of the release commit that carries the package's own version from 2.1.5 to 2.1.6, itself dated 14 August 2026, one day after the fix and one day before this CVE's own eventual 4 September 2026 publication.

Tracking state. The advisory itself carries no structured affected version range for a supported package ecosystem, confirmed directly on the advisory's own page, and this record's own attempts to reach aws.amazon.com, nvd.nist.gov and several third party threat intelligence aggregators were all blocked by this session's own network egress policy, leaving the GitHub Advisory Database and this record's own direct reading of the cloned source as the strongest evidence available to it.

Exploitation. Unknown. No source available to this record states this was exploited against a real deployment, and the advisory's own EPSS score, 0.144 percent, fourth percentile, is consistent with no observed exploitation activity.

Provenance evidence quality. Strong for the mechanism, its introduction and its fix: this record cloned awslabs/mcp directly and read the exact commit that introduced the vulnerable template, the exact commit that fixed it, the exact commit that bumped the affected package to the version the CVE names as fixed, and the current default branch, confirming all four directly rather than inferring any of them from the advisory's own prose. Weaker for the formal CVE and AWS bulletin record themselves: cve.org, nvd.nist.gov and aws.amazon.com were all blocked to direct fetch in this session, so this record relies on the GitHub Advisory Database's own record for CVE and CVSS metadata and does not independently confirm the assigning CVE Numbering Authority or the AWS bulletin's own exact wording.

Where this sits in the pattern

IBM ContextForge's own stored jq filter already showed a value accepted as configuration becoming an executable program at invocation time, evaluated immediately, inside the same live gateway process that stored it. This record's own mechanism shares the same underlying shift, from data to control syntax, without sharing its timing or its principals: nothing here is evaluated inside the tool that stored it, and the two moments that matter, generation and deployment, can be separated by an arbitrary interval, a different host, and a different principal entirely, with the generated artifact itself persisting unchanged in between. git-mcp-server's own read only tools showed a single caller supplied value changing role from data to control syntax purely by virtue of where it sat in an external process's own argument list, decided immediately, inside one function call. This record's own mechanism decides that same question at generation time, but the consequence of that decision does not activate until a later, separate action executes the artifact generation produced. This desk's wider argument that execution authority has to be evaluated against the action a system actually takes extends here across a boundary neither of those two records needed to cross: the authority that matters for this record's own mechanism is not the authority held at generation time at all, but the authority the deployment principal holds later, for an artifact whose exact content that principal may never have reviewed against the data model that produced it.

What this record does not establish

This record does not claim CVE-2026-85654 has been exploited against a real AWS account, that every dynamodb-mcp-server user runs the CDK generator against an untrusted data model file, that the advisory's own AWS Security Bulletin reference carries different or additional detail this record could not read directly, which body served as the assigning CVE Numbering Authority, that the data model's own author is typically an LLM agent rather than a person, or that a deployment host compromised through this mechanism was demonstrably an AWS account of any particular scale. Where the evidence available to this record does not establish a fact, this record states it as unknown rather than inferring it from the pattern this weakness class shows elsewhere.

Sources

This analysis interprets third-party reporting, research and announcements. Moona is not the original reporter of the underlying events.

[2]
awslabs.dynamodb-mcp-server release history
PyPI (Python Package Index) · Primary source
[3]
Commit 46ca139f: fix, dynamodb-mcp-server, validate and escape names in CDK generator (#4384)
awslabs/mcp (GitHub, commit) · 13 August 2026 · Primary source
[4]
Commit eff6cce0: chore, bump packages for release/2026.08.20260813230114 (#4491)
awslabs/mcp (GitHub, commit) · 14 August 2026 · Primary source
[5]
Commit 15e43c67: feat, dynamodb_mcp_server, add CDK app for table creation (#2055)
awslabs/mcp (GitHub, commit) · 14 January 2026 · Primary source
[6]
Commit 0bb449ef: chore, bump packages for release/2026.01.20260115004242 (#2176)
awslabs/mcp (GitHub, commit) · 14 January 2026 · Primary source
[7]
stack.ts.j2 and models.py on the current default branch
awslabs/mcp (GitHub, source) · Primary source

Protocol evidence

This record does not assess these architectures. The connection runs through the Risk Registry requirement each one bears on, and these published authority architectures are what the evidence says about that requirement.

Protocol evidence related through AEW-026 A value accepted as data during generation executes under a later deployer's own authority

  • Supports requirement

    ChainIT Authority Protocol and Agent Subject Profile for pre execution authority validation

    ChainIT

    Requirement A canonical transaction digest is described binding payer, payee, destination, amount, currency or asset and payment rail to approval and execution

    ChainIT's canonical transaction digest binds an approval to the exact fields of the transaction it covers, a corrective this weakness names for a different artifact class: a generated CDK application whose own content a data model name can silently change between the moment it is produced and the moment a deployer gives it effect. This entry's own reading of the affected generator shows no equivalent binding existed, at any point, between the data model an author supplied and the generated stack.ts a deployer later compiled and deployed.

    This record is the cited evidence for this relationship.

    View protocol evidence

  • Supports requirement

    EP Authorization Receipts (EMILIA Protocol)

    Iman Schrock, EMILIA Protocol, Inc., individual submission to the IETF

    Requirement Implementations MUST reject an approval request whose action hash does not match a locally recomputed hash of the presented Action Object

    EMILIA's requirement that an approval be rejected unless the action hash matches a locally recomputed hash of the exact action object is the binding this weakness shows missing across a longer, deferred gap: whoever later runs cdk deploy authorizes the deployment of the CDK application the generator was meant to produce, not a specific, hashed stack.ts whose content that principal separately reviewed. Amazon's own CDK generator, before its fix, is concrete evidence for the consequence of that gap, a persisted generated artifact whose exact content a crafted data model name changed without anyone re-hashing or re-reviewing it before deployment gave it effect.

    This record is the cited evidence for this relationship.

    View protocol evidence

Related Intelligence

All Intelligence Records →