10 ms·
Secure by Design
- haybanusa 7y agoInteresting stuff here. I'm glad to see that some of the conclusions I came up to over time turned out to be named and well-understood best practices. But it seems I have more work to do.
- korpiq 7y agoWhat programming language would cause least friction for providing discrete types for all domain primitives or even all handled data?
- mosen 7y agoI’ve found F# to be good in this regard. Can’t claim that it would have the least friction, however.
- rajandatta 7y agoI've always thought that a language like Idris based on Dependent Types would by far be the best language for this. The problem is a non-trivial one even for 'simple' things like a person's name. Having a rule that takes in languages, special characters, spaces etc is hard.
- korpiq 7y agoThank you, both insights help me in pondering how to ever better ensure quality.
- JadeNB 7y ago> I've always thought that a language like Idris based on Dependent Types would by far be the best language for this. As someone who loves the idea of dependent types, it seems to me that this is the best theoretical solution, but maybe not the best practical solution. If solving a simple-seeming domain problem involves modelling, not just first-order types, but the whole theory of dependent types, then I think people are going to start looking for escape hatches rather than upgrading their mental models.
- fhennig 7y agoI think most statically typed languages should be good for this.
- carlmr 7y agoStatic typing isn't really the only thing here. Strong typing would also be good. E.g. C has a very weak type system, which is static. There's a lot of implicit conversion going on. Also the expressiveness of the type system is very limited (in C++ also). OCaml, F#, Haskell and other functional candidates are strongly and statically typed, with very expressive type systems. Idris with it's dependent types would be ideal and goes even further than the above. In embedded most likely ADA and Rust offer strong enough static type systems.
- gpderetta 7y ago> C has a very weak type system, which is static. There's a lot of implicit conversion going on. Also the expressiveness of the type system is very limited (in C++ also). It is true that the subset that C++ shares to C has too many implicit conversions, but you can do much better. For example in C++ you can use enum class to define strongly typed integrals that do not implicitly convert to the basic types.
- carlmr 7y agoEnum classes are a good first step, but C++ is still very relaxed on integer and float conversions. I just wish I could use rust or ADA at work.
- ronlobo 7y agoAgree, strongly typed languages are best suited for this as it usually allows to embed business domain invariants into the type system which can be checked at compile time. Rust does a great job in that regard and is not too slow.
- frou_dh 7y agoOCaml, F#, Haskell, Elm, ... (anything with lightweight notation to define sum types [sometimes with only 1 variant], and a module system to limit who can construct them) There's a whole well-reviewed book on exactly this: https://pragprog.com/book/swdddf/domain-modeling-made-functional https://pragprog.com/book/swdddf/domain-modeling-made-functi...
- nicoburns 7y agoRust and Swift can also be added to this list.
- tmountain 7y agoNot saying it's the least friction, but Golang can accomplish this pretty easily with interfaces, structs and receivers. In Haskell, you can use phantom types to accomplish this in really elegant way, as is illustrated here: https://wiki.haskell.org/Phantom_type https://wiki.haskell.org/Phantom_type.
- StevenWaterman 7y agoTypeScript is surprisingly good at this. It's not popular as a backend language, but shows that your choices aren't limited to pure FP languages and low-level systems programming languages.
- Normal_gaussian 7y agoTypescript is explicitly bad at this; it is structurally typed, not nominally typed, meaning its effectively useless at enforcing domain guards. See the same program in flow [1] (nominally typed) and TypeScript [2] (structurally typed). In the case of flow the type can only be constructed with the class -thus enforcing the guards - whereas in TypeScript I can accidently (or deliberately) bypass all guards by having a class with an equivalent structure. [1] https://flow.org/try/#0MYGwhgzhAEAKD2ECWAXJA3ApgSQHYswHNMAnaAbwChpp0wQBXTALmlwYFsAjUgbmujB4uCChINgKeCQAU6Vu26kAlBQE0kAMznQAPNAAMy9TWgoAFiXgB3NplsBREldnL+ps+aQQAdHUaY0AC8tAIAvpQRoJAwAHJEYGhYeATEZFQ0-kwKnDwk7oLCouKS0nI5SiSqGaZaOvpGJjQWVra49tBOLjJuJhbefvRMwaE0EVHgUNAAymISKAwk9CAAngAiWpqkmPgpRKRqHvJsuXwmQiJzpbLHinnVTab9vugj6AVjkZSUIJgo0AAPVgIZBJHD4fZkELtWwg1AYcGpUgyACMRn4v3+K1Y8UIiQRezSIxh0Fx+OSELSqPRPz+0AAXqxZiUFksQKsNpotiQdihCQdoR1mfNFst1pttrtKci0b1KACRit+CsRvT+PSRgDeEA https://flow.org/try/#0MYGwhgzhAEAKD2ECWAXJA3ApgSQHYswHNMAna... [2] https://typescript-play.js.org/#code/MYGwhgzhAEAKD2ECWAXJA3ApgSQHYswHNMAnaAbwChpp0wQBXTALmlwYFsAjUgbmujB4uCChINgKeCQAU6Vu26kAlBQE0kAMznQAPNAAMy9TWgoAFiXgB3NplsBREldnL+ps+aQQAdHUaY0AC8tAIAvpQRoJAwAHJEYGhYeATEZFQ0-kwKnDwk7oLCouKS0nI5SiSqGaZaOvpGJjQWVra49tBOLjJuJhbefvRMwaE0EVHgUNAAymISKAwk9CAAngAiWpqkmPgpRKRqHvJsuXwmQiJzpbLHinnVTab9vugj6AVjkZSUIJgo0AAPVgIZBJHD4fZkELtWwg1AYcGpUgyACMRn4v3+K1Y8UIiQRezSIxh0Fx+OSELSqPRPz+0AAXqxZiUFksQKsNpotiQdihCQdoR1mfNFst1pttrtKci0b1KACRit+CsRvT+PSRgDeEA https://typescript-play.js.org/#code/MYGwhgzhAEAKD2ECWAXJA3A...
- StevenWaterman 7y agoThere are ways to enforce nominal typing in TS [1] type Brand<K, T> = K & { __brand: T } type USD = Brand<number, "USD"> type EUR = Brand<number, "EUR"> const usd = 10 as USD; const eur = 10 as EUR; function gross(net: USD, tax: USD): USD { return (net + tax) as USD; } gross(usd, usd); // ok gross(eur, usd); // Type '"EUR"' is not assignable to type '"USD"'. [1] https://michalzalecki.com/nominal-typing-in-typescript/#approach-4-intersection-types-and-brands https://michalzalecki.com/nominal-typing-in-typescript/#appr...
- thesuperbigfrog 7y agoAda does a great job in this regard. Using some examples in the article: >> For example, say that you want to represent the number of books ordered. Instead of using an integer for this, define a class called Quantity. It contains an integer, but also ensures that the value is always between 1 and 240 The Ada code to implement this is: type Quantity is new Integer range 1 .. 240; >> instead of just using a string, define a class called UserName. It contains a string holding the user name, but also enforces all the domain rules for a valid user name. This can include minimum and maximum lengths, allowed characters etc. The Ada code to implement this is: with Ada.Strings.Bounded; package UserName is new Ada.Strings.Bounded.Generic_Bounded_Length (Max => UserName_Max_Length); Dynamic predicates or even a string subtype could be used to further refine the UserName definition depending on exactly what restrictions are needed. While it's not perfect, Ada does make it pretty easy to specify constraints on data types and will complain loudly when the constraints are violated.
- coldacid 7y agoOh my god. I wish C# had this.
- naasking 7y agoAda is pretty much still the only language that has this.
- rauhl 7y ago> Ada is pretty much still the only language that has this. Common Lisp does too: (deftype quantity () '(integer 0 240)) (defun foo (x) (declare (type quantity x)) (1+ x)) (foo 1) → 2 (foo -1) → ERROR (defun valid-username-p (string) (and (< 8 (length string) 24) (every (lambda (char) (find char "abcdefghijklmnopqrstuvwxyz0123456789-_=./" :test #'char=)) string))) (typep "foo" 'username) → NIL (typep "foobarbaz" 'username) → T (typep "foobar-baz" 'username) → T (typep "foobar-baz " 'username) → NIL Common Lisp is pretty awesome.
- ragnese 7y agoAnd a related question: How far is too far and/or impractical? I just recently was working on a backend application and was modeling the database entities. The project uses UUIDs as primary keys. Should each entity have its own primary key type? `Location` gets a `LocationId`, User gets a 'UserId', etc, etc, where they're really all just wrappers around UUID? Honestly, I thought about doing that a bunch of times during the start of the project, but I was pretty sure I'd get some harsh, sideways, glances from the rest of the team.
- maxdeviant 7y agoYes. This is exactly what we do at work. It makes sure that you never pass a `LocationId` where a `UserId` is expected; the type system literally will not allow it.
- nitnelave 7y agoInteresting, but there are quite a few bits that are Java-specific. I'm thinking in particular about exposing mutable objects: they mention that even if you expose an immutable reference, the object can still be mutated, same for collections. This is not the case in C++ or Rust, for instance, let alone in Haskell or similar FP languages.
- machinecoffee 7y agoThis is one of the cool things about NextSteps' Foundation classes (now Cocoa) where they have explicit immutable collection interfaces.
- KingOfCoders 7y agoJava-SDK-specific. There are several immutable data type libraries for Java if you should need them. The main downside of immutable data is that it's much slower than mutable data. A performance oriented solution is borrowing in Rust. (Which after a decade of using Scala is one of it's main downsides).
- jorangreef 7y agoSize. A payload of one million characters should probably be rejected without further analysis. As well as checking the total size, it is good to check the sizes of the parts. Another thing to check, that is often overlooked, is Quantity. Every loop should have a limit. There should be no unbounded object allocation. Usually, it's not the big things that get you but the sheer number of small things. For example, a 20 MB email with 4 million empty attachments: https://snyk.io/blog/how-to-crash-an-email-server-with-a-single-email/ https://snyk.io/blog/how-to-crash-an-email-server-with-a-sin... Further examples that affected ClamAV and SpamAssassin: https://blog.clamav.net/2019/11/clamav-01021-and-01015-patches-have.html https://blog.clamav.net/2019/11/clamav-01021-and-01015-patch... http://mail-archives.apache.org/mod_mbox/spamassassin-announce/201912.mbox/%3C4867fdb1-eaba-98c9-661a-9b5cd974d5c1@apache.org%3E http://mail-archives.apache.org/mod_mbox/spamassassin-announ...
- dkersten 7y ago> Every loop should have a limit. There should be no unbounded object allocation. I’ve recently created some mini-languages (DSL’s) for everyone users to control various software and I’ve found that using a synchronous transformational language[1], which has no unbounded loops (or anything) and is therefore guaranteed to complete, and which runs in a stop-the-world fashion (ie each invocation acts as if atomic, whether it actually is or if implementation tricks like transaction rollback + retries are used) makes it easier both to develop and for end users especially ones who aren’t programmers. I’m a huge fan of this approach for correct and secure software. [1] https://en.m.wikipedia.org/wiki/Synchronous_programming_language https://en.m.wikipedia.org/wiki/Synchronous_programming_lang...
- arethuza 7y agoThe only problem with this approach that I have encountered is that is requires defining fairly arbitrary limits that are either so small that some user will inevitably run into them or so large that you're not getting as much protection as you think. NB I don't know what the answer to this is, but pretty much any time a system I have been involved with contains a "reasonable" limit then people want more than that pretty quickly!
- pintxo 7y ago> Rotate secrets automatically every few hours How is this helpful? To automate it means there is another system that could be attacked and it‘s a valuable one as it manages all the secrets, or not? What‘s the story?
- eeZah7Ux 7y agoNot at all, it's very valuable. It constantly clears out old credentials.
- candeira 7y agoTwo ways in which this is helpful. The list is not exhaustive: 1. The system that gives a service to the public is publicly accessible by default. The system that rotates its credentials it doesn't need to be; it can sit behind your firewall listening only on one port for ssh, with firewall rules allowing access only from a bastion host. 2. The credential rotator also connects to your server and drops credentials into it; you don't call from your server to the credentials rotator, because see Point 1 above. This limits the attack surface. You're right that everything is potentially vulnerable, and so would the credential rotator system. However, by rotating secrets this way, you shift the locus of security to a smaller place that you can defend much better.
- yonixw 7y agoI actually think it talking about a case where the rotator and the rotated are both in same place security wise, Like a schedule task to change login salt, both on the same vm\docker etc. Same goes to 2 apps connecting with a shared secret, making both of them change it wont expose any more components but will add additional layer of security (like google authenticator)
- numbsafari 7y agoMy personal experience, over 20 years, is that if something like credential rotation isn't automated, it simply doesn't happen. If it does happen, it's a major hassle, probably doesn't get done correctly (causes downtime, something gets missed, probably isn't documented, etc.). Also, there's a huge organizational inertia against doing it. So, for example, when an employee leaves, if you don't have this automated, it likely doesn't happen because "why would we do all this work, it's not like they were a bad person and they aren't stupid". If you automate this and run it on an automated schedule < 30 days then it is pretty likely that it won't be causing downtime unexpectedly, that you'll have monitoring in place to make sure it actually gets done, that, even if you forget to trigger it for a specific reason (e.g., aforesaid person leaves the organization) it will happen within a reasonable period of time. In terms of securing such a system... you need to make sure that you separate the system into appropriate pieces with limited access. So, for example, you want a job that is run with an account that only has access to rotate the credentials. It can't use them for anything, just rotate them. Services that consume those credentials should not be able to update them, just use them. You can then ensure that the process that rotates credentials executes in a highly locked-down part of your infrastructure. Indeed, automating this process also encourages you to create processes with limited access, rather than relying on administrators who have so many responsibilities, you probably just throw them in the equivalent of wide-open sudoers file and call it a day. It sounds complicated, but if you have decent abstractions, this kind of stuff is actually pretty easy to accomplish.
- eeZah7Ux 7y ago> Rotate secrets automatically every few hours Good advice! > Repave servers and applications every few hours. This means redeploying the same software – if an attacker has compromised a server, the deploy will wipe out the attacker’s foothold there Yes, but keep in mind that the same attacker will be able to run the same attack successfully again, as long as they have an attack vector. > Repair vulnerable software as soon as possible (within a few hours) after a patch is available. Very good advice, and for that you need somebody to update vulnerable libraries and OS packages - like what Linux distributions do - unless you want to maintain hundred of packages by yourself.
- natmaka 7y ago>> Repave servers and applications every few hours. > Yes, but keep in mind that the same attacker will be able to run the same attack successfully again Exactly. Detecting that deployed files were tampered with can be tricky (one has to take updates into account, and some attacking code may be able to detect this analysis and nurture it with the original version of the files).
- carapace 7y agoAs an aside, s/nurture/neutralize/ eh? I'm seeing more and more malapropisms in online text. Has some common auto-correct thing gotten aggressive in guessing, badly, at misspelled words? (Auto-incorrect.)
- 7y ago
- frou_dh 7y agoI heard a good point about why some numeric thing in your project domain (say, InvoiceNumber) should not be a primitive int: Making it a primitive int implies that ALL the operations available on a primitive int make sense. But it can never make sense to, for example, divide one InvoiceNumber by another InvoiceNumber!
- yonixw 7y agoI remember taking time to make Radian and Degree classes just to make the code clean. Because it was a simple project, it was 10% of the lines but totally worth it.
- mjul 7y agoUsing primitive types “too much” is a frequent anti-pattern. It has a name: Primitive Obsession (https://wiki.c2.com/?PrimitiveObsession https://wiki.c2.com/?PrimitiveObsession) I think people do it because it is quite some work to implement proper value types in common languages like JavaScript, Java and C#. Languages like F# or Kotlin give it to you almost for free. The rule of thumb is exactly as you observed: if values have different semantics they should have different types.
- jen20 7y agoScott Wlaschin goes into detail about this pattern in F# both on his website [1] and in his book, "Domain Modelling Made Functional". The difference between F# and C# in this regard is quite spectacular. [1]: https://fsharpforfunandprofit.com/posts/conciseness-type-definitions/ https://fsharpforfunandprofit.com/posts/conciseness-type-def...
- gitgudnubs 7y agoCreating perfect types for every single input is also an anti-pattern. Most of our needs fall somewhere between "works pretty well" and "formally verified". Riding herd on the type system to prove ever more invariants about your system is usually a waste of time. It's reasonable to put a little more effort into it along API lines. But there's a reason that the compiler doesn't make it easy to define an integer type that can hold values between -7 and 923091.
- loop0 7y agoThis text remembers me of some techniques used on qmail, which has a great security record.
- JdeBP 7y agoDaniel J. Bernstein's original qmail doco and the later security retrospective have come up a number of times on Hacker News. * http://cr.yp.to/qmail/guarantee.html http://cr.yp.to/qmail/guarantee.html * http://cr.yp.to/qmail/qmailsec-20071101.pdf http://cr.yp.to/qmail/qmailsec-20071101.pdf * https://hillside.net/plop/2004/papers/mhafiz1/PLoP2004_mhafiz1_0.pdf https://hillside.net/plop/2004/papers/mhafiz1/PLoP2004_mhafi... * https://blog.acolyer.org/2018/01/17/some-thoughts-on-security-after-ten-years-of-qmail-1-0/ https://blog.acolyer.org/2018/01/17/some-thoughts-on-securit...
- brtkdotse 7y agoNice to see this on HN. I interviewed the authors back in October[1] (in Swedish) and I found their take on designing in security very interesting. [1] https://kompilator.se/017/ https://kompilator.se/017/
- thdespou 7y agoI like the book, but in the other hand I don't like some parts of it. It seems that they circle around DDD and repeating the same concepts. You can find many more DDD related books than this one. I was expecting something more of security related content and mitigation tactics.
- chias 7y ago"Here is a book I read that I thought was good. Here are the things I liked most about it. Here is a high level understanding of why they're great." I love when people write articles like this. Thank you Henrik!
- henrik_w 7y agoThanks chias, that makes me happy!!
- chrisdone 7y ago"Consistent at creation" is also known in the Haskell community by the phrase "make illegal states unrepresentable".
- sitkack 7y agoThis technique should be more widely shared. I stumbled across it years ago in Python, but it can be expressed in nearly any language. https://en.wikipedia.org/wiki/Substructural_type_system https://en.wikipedia.org/wiki/Substructural_type_system
- tabtab 7y agoCustomers have repeatedly and historically valued features over quality (for most niches). If somebody finds a magic formula to have both for a good price, this may change. But so far it hasn't proved consistently possible. I will agree that a well-trained and well-coordinated set of individual software builders can occasionally pull it off economically, but it requires too many things to go right in terms of organization and staff: a lucky accident. Most IT shops are semi-dysfunctional because the usual frailness of human nature wins out over rationality the majority of the time. Dilbert™ is life, life is Dilbert™.
- sitkack 7y agoAt least temporarily, hopefully for some number of years, people's risk assessment philosophy will have changed. Maybe we will start paying attention to global warming and the level of effort needed to avoid catastrophe.
- tabtab 7y agoNope. As a species we tend to over-prepare for the "last war", rather than basing them on actual risk probabilities.
- machinecoffee 7y agoMy favourite line from this is: “Any integer between -2 billion and 2 billion is seldom a good representation of anything.” I think that's something Ada got really right.