[{"data":1,"prerenderedAt":316},["ShallowReactive",2],{"navigation":3,"/blog/security-in-product-requirements":141,"/blog/security-in-product-requirements-surround":305},[4,36,55,80,101,116,128],{"title":5,"path":6,"stem":7,"children":8,"icon":35},"ASP.NET Core","/aspnet-core","1.aspnet-core/1.index",[9,11,15,19,23,27,31],{"title":10,"path":6,"stem":7},"Cursor Pagination",{"title":12,"path":13,"stem":14},"Dapper - Micro ORM","/aspnet-core/dapper","1.aspnet-core/2.dapper",{"title":16,"path":17,"stem":18},"DbUp - Database Migrations","/aspnet-core/dbup","1.aspnet-core/3.dbup",{"title":20,"path":21,"stem":22},"Serilog - Structured Logging for .NET","/aspnet-core/serilog","1.aspnet-core/4.serilog",{"title":24,"path":25,"stem":26},"Class Inheritance in C#","/aspnet-core/inheritance","1.aspnet-core/5.inheritance",{"title":28,"path":29,"stem":30},"C# Collections","/aspnet-core/collections","1.aspnet-core/6.collections",{"title":32,"path":33,"stem":34},".NET gitignore Command","/aspnet-core/gitignore","1.aspnet-core/7.gitignore",false,{"title":37,"path":38,"stem":39,"children":40,"icon":35},"Oauth2.0 and OpenIdConnect","/oauth2andopenidconnect","10.oauth2andopenidconnect/1.index",[41,43,47,51],{"title":42,"path":38,"stem":39},"RFC 6749 - OAuth 2.0 Authorization Framework",{"title":44,"path":45,"stem":46},"Authentication (AuthN) vs Authorization (AuthZ)","/oauth2andopenidconnect/authnandauthz","10.oauth2andopenidconnect/2.authnandauthz",{"title":48,"path":49,"stem":50},"TOTP Authentication","/oauth2andopenidconnect/totp","10.oauth2andopenidconnect/3.totp",{"title":52,"path":53,"stem":54},"OAuth 2.0 Resource Indicators - RFC 8707","/oauth2andopenidconnect/oauth2resourceindicators","10.oauth2andopenidconnect/4.OAuth2ResourceIndicators",{"title":56,"path":57,"stem":58,"children":59,"page":35},"Azure","/azure","2.azure",[60,64,68,72,76],{"title":61,"path":62,"stem":63},"Azure Cost Management","/azure/azuremanagementandgovernance","2.azure/1.Azuremanagementandgovernance",{"title":65,"path":66,"stem":67},"Azure Policy","/azure/azurepolicy","2.azure/2.azurepolicy",{"title":69,"path":70,"stem":71},"Code Blocks","/azure/code-blocks","2.azure/3.code-blocks",{"title":73,"path":74,"stem":75},"Prose Components","/azure/prose-components","2.azure/4.prose-components",{"title":77,"path":78,"stem":79},"Images and Embeds","/azure/images-embeds","2.azure/5.images-embeds",{"title":81,"path":82,"stem":83,"children":84,"page":35},"Git","/git","3.git",[85,89,93,97],{"title":86,"path":87,"stem":88},"Git Rebase","/git/git-rebase","3.git/1.git-rebase",{"title":90,"path":91,"stem":92},"Git Stash","/git/git-stash","3.git/2.git-stash",{"title":94,"path":95,"stem":96},"SemVer","/git/semver","3.git/3.semver",{"title":98,"path":99,"stem":100},"Conventional Commits","/git/conventional-commits","3.git/4.conventional-commits",{"title":102,"path":103,"stem":104,"children":105,"icon":35},"Design Patterns","/design-patterns","7.design-patterns/1.index",[106,108,112],{"title":107,"path":103,"stem":104},"Introduction",{"title":109,"path":110,"stem":111},"Installation","/design-patterns/installation","7.design-patterns/2.installation",{"title":113,"path":114,"stem":115},"Usage","/design-patterns/usage","7.design-patterns/3.usage",{"title":117,"path":118,"stem":119,"children":120,"icon":35},"Software Principles","/principles","8.principles/1.index",[121,122,125],{"title":107,"path":118,"stem":119},{"title":109,"path":123,"stem":124},"/principles/installation","8.principles/2.installation",{"title":113,"path":126,"stem":127},"/principles/usage","8.principles/3.usage",{"title":129,"path":130,"stem":131,"children":132,"icon":35},"Software Architecture","/architecture","9.architecture/1.index",[133,135,138],{"title":134,"path":130,"stem":131},"CQRS Pattern",{"title":109,"path":136,"stem":137},"/architecture/installation","9.architecture/2.installation",{"title":113,"path":139,"stem":140},"/architecture/usage","9.architecture/3.usage",{"id":142,"title":143,"body":144,"date":294,"description":295,"extension":296,"featured":35,"meta":297,"navigation":298,"path":299,"readTime":300,"seo":301,"stem":302,"tag":303,"__hash__":304},"blog/blog/7.security-in-product-requirements.md","A Secure Implementation Cannot Save an Insecure Product Decision",{"type":145,"value":146,"toc":281},"minimark",[147,151,154,159,162,179,183,186,190,193,202,206,209,213,216,220,228,232,235,238,242],[148,149,150],"p",{},"Most security conversations happen at the code review stage: is input validated, are dependencies patched, does this endpoint leak anything it shouldn't. Those questions matter. But by the time a pull request exists, the most consequential security decision may already be locked in — because it was made in a product requirements document, weeks or months before anyone wrote a line of code.",[148,152,153],{},"A team can implement a specification flawlessly and still ship something insecure, if the specification itself was built on a security assumption that stopped being true years ago. Security isn't only a coding problem. Sometimes the vulnerability was designed in on purpose, by people who had no idea they were introducing one.",[155,156,158],"h2",{"id":157},"the-requirement-is-part-of-the-security-boundary","The Requirement Is Part of the Security Boundary",[148,160,161],{},"Take a familiar example: \"users must answer security questions to recover their account.\" An engineering team can build this feature to a genuinely high standard — encrypt the stored answers, rate-limit recovery attempts, log every recovery event, monitor for abuse, protect the API surface end to end. Every one of those controls can pass review and testing. None of it touches the actual question: is the recovery mechanism itself sound? If the \"secret\" an attacker needs is something they can find in ten minutes of searching, no amount of implementation quality changes the answer.",[148,163,164,165,172,173,178],{},"This isn't hypothetical or dated. ",[166,167,171],"a",{"href":168,"rel":169},"https://pages.nist.gov/800-63-4/sp800-63b.html",[170],"nofollow","NIST's Digital Identity Guidelines"," — the federal standard that much of the industry treats as a baseline — explicitly prohibit knowledge-based authentication as a recovery or verification mechanism. The reasoning is direct: answers to questions like \"what was your first pet's name\" or \"what city were you born in\" are disproportionately available through social media, public records, and the enormous volume of data already sitting in breach dumps. One ",[166,174,177],{"href":175,"rel":176},"https://authlayer.dev/blog/nist-800-63-4-authentication-requirements/",[170],"analysis of the standard's evolution"," put it plainly: searching a person's name alongside a hometown or school on public sources now surfaces answers to the majority of a typical security-question set — before an attacker has done anything more sophisticated than a search engine query. The problem was never how the answer is stored. It's whether the answer was ever a real secret.",[155,180,182],{"id":181},"engineers-cannot-fix-a-requirement-they-didnt-write","Engineers Cannot Fix a Requirement They Didn't Write",[148,184,185],{},"This is where the usual security workflow quietly breaks down. Product defines the requirement, engineering builds it, QA verifies the behavior matches spec, security reviews the implementation, and the feature ships. Every person in that chain can do their job correctly. If the requirement itself encoded an outdated security model, the weakness survives the entire process untouched — because nobody in that pipeline was actually positioned to question whether the feature should exist in its current form. Code review answers \"is this implementation safe?\" It doesn't answer \"is this feature design still considered safe?\" — and by the time code review happens, that second question usually isn't on the table anymore.",[155,187,189],{"id":188},"security-guidance-has-to-reach-the-requirements-stage-not-just-the-code-review-stage","Security Guidance Has to Reach the Requirements Stage, Not Just the Code Review Stage",[148,191,192],{},"This is precisely why security guidance can't be treated as something engineering consults during implementation and product teams never see. NIST's guidance on knowledge-based authentication didn't change quietly — it was a documented, public reversal, the kind of update that a security-aware product process should catch automatically, the same way a dependency scanner catches a deprecated package. But a product requirements document written five years ago doesn't get a CVE alert when the security assumptions underneath it expire. Someone has to go looking, and that someone increasingly needs to sit upstream of engineering, not downstream of it.",[148,194,195,196,201],{},"CISA's ",[166,197,200],{"href":198,"rel":199},"https://www.cisa.gov/securebydesign/pledge",[170],"Secure by Design pledge"," — signed by dozens of major software manufacturers — frames this exact shift as an industry-wide priority: security should be treated as a core business requirement embedded during the design phase, not a technical feature bolted on afterward or verified only at the review stage. The pledge's own framing is worth taking literally: products should arrive secure by default, with the expensive, hard-to-reverse decisions about authentication, defaults, and data handling made correctly before a single feature ships — because retrofitting security into a shipped product is a fundamentally different (and more expensive) problem than designing it in from the requirement forward.",[155,203,205],{"id":204},"requirements-age-the-same-way-code-does","Requirements Age the Same Way Code Does",[148,207,208],{},"Security recommendations aren't static, because the conditions they respond to aren't static. Attackers develop new techniques. Data that used to be private becomes searchable. Platforms add authentication capabilities that didn't exist five years ago — passkeys, hardware security keys, and authenticator apps are all comparatively recent, and standards bodies have moved decisively in their direction while moving decisively away from the mechanisms they replace. A requirement that was defensible when it was written can become the weakest part of a system purely by standing still while the threat landscape and the available tooling both moved on. Treating a requirements document as a one-time artifact, reviewed once at creation and never revisited, guarantees that this drift accumulates silently until an incident forces the conversation nobody scheduled.",[155,210,212],{"id":211},"sometimes-the-fix-isnt-a-better-implementation-its-a-different-requirement","Sometimes the Fix Isn't a Better Implementation — It's a Different Requirement",[148,214,215],{},"The most effective security intervention in a case like this often isn't a pull request at all. It's a product decision: replacing \"answer three security questions\" with a genuinely strong recovery mechanism — verified email or device-based recovery, an authenticator app, a hardware key — rather than trying to shore up a fundamentally weak mechanism with better encryption and more logging around it. A perfectly implemented version of the wrong design is still the wrong design. Organizations that pour effort into securing the implementation layer while leaving the underlying product decision untouched are optimizing the part of the problem that was never actually broken.",[155,217,219],{"id":218},"security-belongs-in-the-same-conversation-as-usability-and-scalability","Security Belongs in the Same Conversation as Usability and Scalability",[148,221,222,223,227],{},"Product teams already treat usability, performance, and scalability as first-class questions asked ",[224,225,226],"em",{},"during"," design, not bolted on after a feature ships. Security deserves the same standing, asked with the same two-sided framing: not just \"can users complete this workflow,\" but \"can attackers abuse this workflow\" — asked by the people writing the requirement, not deferred entirely to whoever reviews the code six weeks later. The strongest security teams don't only review implementations. They review the assumptions requirements are built on, and they're willing to ask whether a given approach is still the right one given current guidance, not just whether it was defensible when it was first written down.",[155,229,231],{"id":230},"the-takeaway","The Takeaway",[148,233,234],{},"A secure implementation cannot compensate for an insecure product decision. That's not a criticism of engineering — a team building exactly what was specified, to a high technical standard, can still inherit a vulnerability that existed before their first commit. The most expensive security failures are rarely caused by careless code. They're caused by building the wrong thing correctly, at scale, for years, because the assumption behind the requirement was never revisited after the world it was written in had already changed.",[236,237],"hr",{},[155,239,241],{"id":240},"references-and-further-reading","References and Further Reading",[243,244,245,257,265,273],"ul",{},[246,247,248,249,256],"li",{},"NIST, ",[166,250,252,255],{"href":168,"rel":251},[170],[224,253,254],{},"Digital Identity Guidelines"," (SP 800-63B)"," — the current federal standard explicitly prohibiting knowledge-based authentication as a verifier mechanism.",[246,258,259,260,264],{},"AuthLayer, ",[166,261,263],{"href":175,"rel":262},[170],"\"NIST SP 800-63-4 Is Final: What the New Digital Identity Guidelines Actually Change\""," — a plain-language breakdown of why knowledge-based authentication was deprecated industry-wide.",[246,266,267,268,272],{},"CISA, ",[166,269,271],{"href":198,"rel":270},[170],"Secure by Design Pledge"," — the industry framework for treating security as a design-phase business requirement rather than a post-hoc technical feature.",[246,274,267,275,280],{},[166,276,279],{"href":277,"rel":278},"https://www.cisa.gov/securebydesign",[170],"Secure by Design"," — CISA's broader initiative and supporting guidance on building security in from the earliest stages of the product lifecycle.",{"title":282,"searchDepth":283,"depth":284,"links":285},"",1,2,[286,287,288,289,290,291,292,293],{"id":157,"depth":284,"text":158},{"id":181,"depth":284,"text":182},{"id":188,"depth":284,"text":189},{"id":204,"depth":284,"text":205},{"id":211,"depth":284,"text":212},{"id":218,"depth":284,"text":219},{"id":230,"depth":284,"text":231},{"id":240,"depth":284,"text":241},"2026-08-02","A team can build a specification flawlessly and still ship something insecure, if the specification itself was built on a security assumption that stopped being true years ago.","md",{},true,"/blog/security-in-product-requirements","7 min",{"title":143,"description":295},"blog/7.security-in-product-requirements","Security","PZDAUqCERlZXtIupvmEUjjSpMVskv4jQ1Lcd_iinGtc",[306,311],{"title":307,"path":308,"stem":309,"description":310,"children":-1},"Password Security Was Never About Complexity — It's About Exposure","/blog/password-security-not-complexity","blog/6.password-security-not-complexity","Complexity rules check whether a password looks hard to guess. Attackers running credential stuffing don't care how a password is shaped — only whether it's already sitting in a breach dataset.",{"title":312,"path":313,"stem":314,"description":315,"children":-1},"Multiple Wrongs Do Not Make a Right: Challenging Industry Patterns in Banking Software","/blog/multiple-wrongs-banking-patterns","blog/8.multiple-wrongs-banking-patterns","\"Other banks do it this way\" answers is this common, not is this the right control. Two onboarding requirements worth interrogating on their own merits, not on precedent.",1785744836678]