Spec it safe: shift left and harden your specs with security requirements

By
RA

Rajdeep Mukherjee

Applied Science

DE

Debashis Das

Information Security

The SDD moment, and its blind spot

Spec-driven development (SDD) is changing how large teams build software. Instead of relying on a prompt alone, you author a specification that guides an AI coding agent through design, tasks, and implementation; a flow we abbreviate here as Specify → Plan → Execute. But most specs describe what the software should do, the features a user can see and exercise, and don’t mention how it should withstand misuse. Functional requirements are naturally self-demonstrating: if login is broken, someone notices. Security requirements are often not; an app can pass every functional check while leaving an injection or authorization flaw wide open, because nothing exercises the failure case. So when a security constraint is missing from the spec, the agent has no security-focused target to build and iterate against. It falls back on whatever the framework defaults happen to do, which may be sufficient or may leave a hole, and nothing in the spec forces the question either way.

Closing that gap isn’t about adding a better model or tacking on a security review at the end. It’s about moving the work earlier, to a layer you fully control: the specification. When you write a security obligation into the spec, the coding agent has something concrete to build toward and iterate against, instead of leaving it for a reviewer to catch after the code already exists. That’s the shift from treating security as an afterthought, bolted on once the design is settled, to designing for it from the start. It typically pays off in the architecture too. Stating a security requirement early can point you toward a cleaner design, one where meeting the requirement is simpler because you built around it rather than retrofitting later. Specification quality is something you can shape before any code exists, and that is what specification hardening was designed to do.

Introducing specification hardening

We introduce a Kiro Power for hardening specifications, a plugin that adds one extra step to the SDD workflow. After your functional requirements are written and before planning starts, it reads your EARS requirements, flags the types of weaknesses their wording hints at, and proposes security obligations you can trace and test. Those proposals come from a curated library that maps specific CWE weakness classes to reusable security obligations, so what specification hardening suggests is based on a known weakness rather than a model’s guess. Relevance comes from the requirements themselves: specification hardening proposes an obligation only when your initial wording points to the weakness it addresses, such as user input, authentication, or data handling, so you see constraints that fit what the application does. Security goes into the spec itself, not into a later code review, although we highly recommend you include a security review step after too.

Loading image...Flow diagram titled ‘Kiro Spec-Driven Development’ with five boxes joined left to right by arrows: Specify (EARS requirements), harden the spec, Plan (design + tasks), Execute (code generation), and Implementation candidate — needs conformance + security testing; a note under the ‘harden the spec’ box reads ‘security enters here — before code exists’.
Figure 1. Specification hardening inserts one hardening step into Kiro’s Specify → Plan → Execute flow, at an early pre-code control point.

Specification hardening is an upstream control point, not a runtime security boundary. It reduces the chance that a common weakness class is omitted and makes security intent explicit, traceable, and testable. It does not verify the generated code, prove the absence of vulnerabilities, or replace SAST, DAST, dependency scanning, or human review.

What specification hardening targets: weakness classes that are both expressible as a requirement and detectable from EARS wording: injection, missing authentication, weak credential storage, unencoded output, and similar omissions.

What specification hardening does not target: weaknesses that depend on data-flow or code-level analysis, authorization gaps that are not lexically signaled, and anything about the runtime, infrastructure, or dependencies. Those still need a human reviewer and downstream tooling.

The heart of the specification hardening engine: the CWE-to-spec mapping library

Not every weakness can be fixed in a spec; many are architectural or only visible in code. But a selected set of requirements-expressible CWE classes can be mapped to reusable security obligations that mitigate the common form of each weakness when correctly implemented and verified. Specification hardening ships these mappings as a deterministic TypeScript library and CLI, so every constraint is reproducible.

CWEWeaknessTrigger conditionRequired EARS constraint
CWE-89SQL InjectionRequirement queries a database using external inputExecute all queries via parameterized / prepared statements only; never concatenate.
CWE-862Missing AuthorizationReturns or mutates a resource owned by a principalVerify the requesting principal owns the resource or holds an explicit grant.
CWE-306Missing AuthenticationExposes an endpoint or resourceRequire a valid authenticated session or signed token before processing.
CWE-79Cross-Site ScriptingRenders user-supplied content in a browserContext-encode all output; add a Content Security Policy header as defense in depth.
CWE-522Insufficiently Protected CredentialsStores or verifies passwordsHash passwords with a strong adaptive function (never plaintext or reversible).
CWE-434Unrestricted File UploadAccepts file uploadsValidate magic bytes against a MIME allowlist; cap size; store outside web root.
CWE-639IDOR / BOLAFetches an object by caller-supplied identifierVerify the object’s owning tenant matches the authenticated caller before returning.

The library ships 60 CWE mappings across eight security domains (injection, authentication, authorization, data exposure, cryptography, input validation, security misconfiguration, and business logic). The weaknesses come from MITRE’s Common Weakness Enumeration (CWE); their selection and detector patterns draw chiefly on the OWASP Top 10:2021 and the Amazon Q Developer Detector Library (formerly CodeGuru). Each mapping carries an OWASP category and a manually assigned severity. The constraints in the table are representative paraphrases, not complete cross-architecture controls.

How the engine works

Specification hardening runs a three-stage pipeline over each requirements file. The engine is deterministic: with the same tool version, mappings, options and unhardened input, the same spec always gets the same hardening. The agent’s semantic review that follows (Figure 2) is not deterministic.

1. Trigger detection

The engine matches each requirement against 60 CWE mappings using two lexical signals: substring keywords (“database”, “upload”, “session”) and regex patterns. It combines the hits into a bounded score, and a CWE is flagged when the score clears a threshold (0.3 by default). Matching is lexical, not semantic, so it can miss weaknesses the wording doesn’t explicitly describe.

2. Coverage check

For each flagged CWE, the engine checks whether the spec already states the needed primitive, such as a parameterized-query clause, an ownership check or output encoding. Only uncovered gaps produce proposals. The check is file-wide and lexical, so a control stated for one requirement can suppress a proposal for another.

3. Constraint proposal

Instead of blocking, the engine inserts each missing constraint as an EARS item. It fills placeholders from the source requirement where it can and falls back to generic wording (“a user”, “the resource”) when it can’t. Each inserted line includes its proposal ID and a [SECURITY: …] tag, so the author can review the changes as a diff. Some mappings layer controls: CWE-89 adds both a parameterized-query clause and an input-validation clause. The programmatic API returns a structured report with each constraint’s CWE, priority and rationale. The CLI writes the constraints in place and doesn’t save that report.

Specification hardening reads only the spec, not the code. It proposes the CWE-derived constraints that the requirement text signals, and the author confirms them against the running system. Making those invariants explicit makes them easier to preserve and test.

Loading image...Workflow diagram in two bands. The top purple band, ‘Specification Hardening Engine · Kiro Power that runs over requirements.md’, flows left to right: Requirements (requirements.md in EARS, with a dashed ‘review marker’ box noting a re-run hardens only new or changed requirements), Detect CWE triggers (keywords + regex patterns vs. 61 CWE mappings, score ≥ 0.3 means the CWE applies), Close coverage gaps (checks each CWE constraint against the whole spec and writes missing ones as EARS in the requirement’s terms), and Hardened spec (edited in place, [SECURITY:] items nested plus a Security Baseline). A ‘weak signal’ arrow drops from Detect CWE triggers into the bottom orange band, ‘Agent semantic review’, which notes that lexical matching can’t see a missing ownership or tenancy check and flows through Review candidates (weak hits scoring 0.05–0.3 on object-level authorization CWEs such as CWE-639/862), Boundary sweep (is caller-to-resource access constrained on every principal-owned resource?), and Add constraint (each confirmed gap becomes a [SECURITY:] EARS item in the domain’s terms, such as ‘active care relationship’), which feeds back up into Hardened spec. A dashed loop across the top reads ‘Spec changed → re-run on the same file (only new/changed requirements hardened)’.
Figure 2. The end-to-end workflow. The engine (purple) matches each requirement against 60 CWE mappings and writes the missing constraints as EARS [SECURITY:] items. The agent’s semantic review (orange) checks weak authorization hits and adds domain-specific constraints for each gap it confirms. When the spec changes, re-running the engine on the same file hardens only the new or changed requirements; the review marker tells it which requirements it has already reviewed (dashed loop).

The easiest way to see why this matters is to watch the same feature ship twice: once from a functionality-only spec, once from a spec hardened by the specification hardening engine. A digital-health startup is building a patient portal where a physician looks up clinical notes by patient name. The product manager writes the requirement most would write:

Requirement: minimally functional and security-silent (EARS, event-driven)

Loading code example...

It reads as complete, but it is only minimally functional. It says nothing about matching semantics, pagination, empty results, which fields may be returned, or who may run it. A coding agent could produce an Express.js endpoint like this (illustrative pseudocode):

Without explicit security constraints (illustrative pseudocode)

Loading code example...

Two weaknesses are embedded here. The string interpolation in the SQL clause is textbook CWE-89: SQL Injection (an input such as %' OR '1'='1' -- adds an always-true clause and comments out the rest, returning every row; exact syntax varies by database). And the endpoint has no authentication check at all, which is CWE-306, Missing Authentication for Critical Function. A third, deeper issue, whether an authenticated clinician should see every patient’s notes, comes up later. The omission affects the requirements, and the implementation wasn’t defensive by default.

Now run the same requirement through the specification hardening engine. Its lexical detector flags two weakness classes (CWE-89 SQL Injection and CWE-306 Missing Authentication) and writes seven sub-numbered EARS constraints back into the requirement, each with a traceable proposal ID and a [SECURITY: …] tag, as shown below. A clause proposed under three or more requirements is moved once into a shared ## Security Baseline (Cross-Cutting) section; this is a heuristic, so check it in the diff.

Specification hardening output after review, written back into requirements.md. The engine adds seven obligations across two CWEs: 1.1–1.2 for CWE-89 (parameterized-query), 1.3–1.4 for CWE-89 (input-validation), and 1.5–1.7 for CWE-306 (authentication). The review pass, done by the Kiro agent in the Power or by you on the CLI, then adds 1.8 (data-minimization, CWE-200) and 1.9 (care-relationship authorization, CWE-862). Representative clauses are shown; the rest are omitted for brevity. The programmatic API also returns a structured report with each engine constraint’s CWE, priority, and rationale; the CLI does not persist that report.

Loading code example...

Reading the IDs: SEC-REQUIREMENTS-REQ-1-EC-89-1-1 reads left to right as SEC (a security constraint), REQUIREMENTS-REQ-1 (the first requirement in requirements.md; in a spec with “Requirement N” headings this becomes REQ-1.1, criterion 1 of Requirement 1), and EC-89-1 (the first EARS constraint template in the CWE-89 mapping). The trailing 1 numbers the clause and appears only when one template expands into several EARS lines, which is why the single-clause EC-306-1 has no suffix. Clauses added in review use SEM in place of EC, e.g. SEM-862-1.

A developer reviews the hardened spec diff and uses judgment on each clause. The engine’s clauses cover injection and authentication, but nothing in the wording told it to limit the returned fields, so the review adds 1.8 (running with --semantic-all also lists CWE-200 as a candidate). Once the spec looks good, the agent generates code from it, building against explicit obligations instead of guessing. This candidate gates the endpoint behind a fail-closed 401 (1.5–1.7), validates the search term (1.3), binds it as a parameter with an explicit LIKE escape (1.1–1.2), and returns only the fields 1.8 names:

Conformance draft: satisfies the parameterized-query (1.1–1.2), input-validation (1.3), authentication (1.5–1.7), and data-minimization (1.8) clauses, but it is NOT yet safe for real patient data: it does not yet enforce 1.9.

Loading code example...

Just as important is what the tool flagged but did not write. Any authenticated physician can read any patient’s notes, because there is no care-relationship check: a missing authorization check (CWE-862) that scores below the threshold. Instead of writing it, the tool surfaces it as a semantic candidate for the reviewer to confirm. In the Kiro Power that reviewer is the agent; on the CLI it is you. The confirmed gap becomes clause 1.9, specialized to the domain: “return clinical notes only for patients with whom the requesting physician has an active care relationship”. The query then becomes authorization-scoped:

Authorization-scoped query: the parameterized, authenticated read plus the review-added care-relationship predicate (1.9, CWE-862). It assumes a PostgreSQL locale/collation with the required ILIKE behavior; schema and policy are illustrative.

Loading code example...

Deterministic hardening surfaced seven obligations across two CWEs here. The data-minimization rule and the domain-specific authorization rule still came from review.

Specification hardening augments security judgment, it does not replace it.

How you run it

Specification hardening is packaged as a Kiro Power, available in the sample-kiro-spec-security-hardening repository on GitHub. You can run it two ways.

Through the Kiro Power. Once your functional requirements are final, ask Kiro to “harden my specs”. The harden-spec skill:

  • finds the bundled engine and builds it on first use;
  • runs it over your .kiro/specs/*/requirements.md;
  • does the semantic review pass for missing authorization checks that keyword matching can’t see;
  • summarizes the constraints it added and any warnings that need your attention.

From the CLI. Build once with npm install in cwe-to-spec-mapping-library/. Then point the compiled CLI at a spec file or a specs directory, using absolute paths:

Loading code example...

The --semantic-candidates flag prints weak authorization matches for review but doesn’t write them to the file.

Both paths edit requirements.md in place. That means hardening fits into version control and code review. The engine only adds lines; it never changes, splits or reorders a requirement, and it reports requirements that aren’t strict EARS instead of rewriting them. Always review the git diff. Re-running is incremental: a hidden review marker records the requirements already hardened, so specification hardening hardens only new or changed ones and doesn’t re-propose constraints a reviewer removed. Run it on the spec as it is; you never need a pre-hardening copy.

Loading image...Kiro IDE power detail page for ‘specification-hardening-engine’ by Rajdeep Mukherjee, with Try power, Uninstall, and Check for updates buttons; tags for security, spec-driven development, EARS, CWE, OWASP, requirements hardening, shift left, and Kiro power; a description saying it hardens Kiro specs with security primitives before code is written; a GitHub Repository button; and a Skills list with two entries, harden-spec and security-primitives-reference, each with a one-line description.
Figure 3. The installed specification hardening Power in Kiro IDE.
Loading image...Kiro IDE chat in which the user types ‘harden the specs’; Kiro replies that it will use the spec security hardening power and activate it first, and an expanded Kiro Powers tool result shows the harden-spec skill’s frontmatter (name harden-spec, license MIT-0, author Rajdeep Mukherjee, version 1.0.0) followed by the heading ‘Harden a Spec with Security Primitives’ and text saying it runs the bundled Specification Hardening Engine over EARS-format requirements to find missing security constraints.
Figure 4. Triggering the specification hardening Power in Kiro IDE.
Loading image...Kiro Requirements Analysis panel quoting an event-driven requirement, ‘WHEN the Chat_Server kicks a Member, THE Chat_Server SHALL create a System_Message that names the kicked user, the Moderator and the reason when the Moderator gave one’, with a View Requirement link, then a question about Requirement 16.5 asking whether the message must omit reason text when no reason is given, two answer options (the first, ‘must omit reason content… (equality semantics)’, is selected), and Submit and Cancel buttons.
Figure 5. Running Requirements Analysis on hardened requirements.

Where specification hardening fits: greenfield, brownfield, and modernization

Because specification hardening operates on the spec rather than the code, the same hardening step slots into three kinds of work. They differ only in where the requirements come from and when specification hardening runs:

Greenfield (new projects). Hardening runs as a step in the spec workflow, before any code is written: given the EARS requirements, specification hardening adds the missing [SECURITY: …] constraints, and the agent implements against the hardened spec. Security intent is present in the spec from the first commit rather than retrofitted later.

Brownfield (existing code). When a requirement is added or changed, re-run specification hardening on the existing spec. It hardens only the new or changed requirements and leaves the ones already reviewed untouched. The added SEC- constraints show up as a reviewable diff alongside the feature work, so the change stays scoped to what was modified. Because specification hardening reads the spec, not the code, check each proposed obligation against how the existing system actually behaves. Where they differ, you’ve found either a gap in the code or a constraint to adapt.

Spec-driven modernization (legacy rewrites). Requirements are first extracted from the legacy system into EARS specs, for example with AWS Transform; specification hardening then batch-hardens that recovered set before re-implementation. The modernized system is rebuilt to an explicitly hardened specification instead of inheriting the original gaps.

Loading image...Three side-by-side columns, each a vertical three-step flow with the same orange hardening step in the middle. Greenfield (new project), ‘Harden every new spec, before code’: Write EARS requirements, hardens the spec (pre-code), Agent builds to the hardened spec. Brownfield (existing code), ‘Harden the spec guarding each change’: Add / edit a requirement, hardens the changed spec, Implement · review SEC- diff. Modernization (legacy → spec-driven), ‘Batch-harden recovered specs’: Reverse-engineer specs from legacy code, hardens the recovered specs, Re-implement to the hardened spec.
Figure 6. Where specification hardening is triggered across the three project types. The hardening step (orange) is identical in each; only the origin of the spec it reads differs.

Concluding remarks

Specification hardening is an early step into a fairly new area: securing the specification itself and using it to steer coding agents toward secure code by design. We think the spec is the right place to start, because it’s the earliest artifact both people and agents work from. But specification hardening is a research prototype and a hypothesis, and the blog makes the case for spec-level controls.

Spec hardening also isn’t the only way to get secure code out of an agent, and it works best alongside the others. Threat modeling catches design-level risks that keyword matches can miss. Secure coding guidance in the agent’s context, such as Kiro steering files, shapes code as it’s written. Frameworks with safe defaults remove whole classes of mistakes. Downstream, SAST, DAST, dependency scanning, AI-aided code security scanning (such as provided by AWS Continuum), security tests and human review still check what was built. Specification hardening adds one more layer, at the point where intent gets written down.

The hardened spec stays in the repository, so the reasoning behind each obligation is still there to audit after the tasks run. If your team is adopting SDD, add a spec security review to your definition of done. Agents can build security in from the start, but only if we tell them what that means.