
Secure by Design - henrik_w
https://henrikwarne.com/2020/03/22/secure-by-design/
======
jorangreef

      Size. 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-
sin...](https://snyk.io/blog/how-to-crash-an-email-server-with-a-single-
email/)

Further examples that affected ClamAV and SpamAssassin:

[https://blog.clamav.net/2019/11/clamav-01021-and-01015-patch...](https://blog.clamav.net/2019/11/clamav-01021-and-01015-patches-
have.html)

[http://mail-archives.apache.org/mod_mbox/spamassassin-announ...](http://mail-
archives.apache.org/mod_mbox/spamassassin-
announce/201912.mbox/%3C4867fdb1-eaba-98c9-661a-9b5cd974d5c1@apache.org%3E)

~~~
arethuza
The 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!

~~~
jorangreef
The problem you describe is not actually specific to this approach, the same
would apply to any Size check, or any limit in general.

In fact, I think the problem is less relevant to a Quantity check, where an
order of magnitude headroom above normal usage goes a long way, more so than a
Size check.

For example, do you know of people who receive 10,000 attachments per email?
This limit would be far and away above any reasonable usage and yet provide
decent protection at the same time.

~~~
arethuza
It's essentially the arbitrariness of that 10,000 number that I'm queasy
about.

e.g. When I thought of a "reasonable" maximum number of attachments on an
email I'd probably say 64.

~~~
jorangreef
"When I thought of a reasonable maximum number of attachments on an email I'd
probably say 64."

We should not be talking of a "reasonable" maximum at all.

Rather, the maximum should be orders of magnitude away from the "reasonable".

At this distance, any arbitrariness quickly fades into meaninglessness.

Detached from the "reasonable" usage, the maximum then becomes informed by
processing cost and complexity analysis, which are more concrete.

~~~
machinecoffee
I would disagree, a sensible limit that cannot overwhelm the machine even if a
multi threaded server is processing its maximum number of emails, should be
enforced.

The correct response to a breach of this limit would be for a reply email to
explain the limit and why the email was rejected.

The user then could send multiple emails with their attachments, and the
system can be sized to handle e.g. 24 threads processing 64 attachments of a
maximum size of e.g. 4096kb

That's how you ensure a system sized to your hardware and ensure maximum
throughput for all users.

~~~
jorangreef
"That's how you ensure a system sized to your hardware and ensure maximum
throughput for all users."

Sure, I think we are actually in agreement. That's exactly what I meant by
"processing cost and complexity analysis".

------
chias
"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
Thanks chias, that makes me happy!!

------
frou_dh
I 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!

~~~
mjul
Using 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
Scott 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-
def...](https://fsharpforfunandprofit.com/posts/conciseness-type-definitions/)

------
nitnelave
Interesting, 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.

~~~
KingOfCoders
Java-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).

------
eeZah7Ux
> 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
>> 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
As 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.)

~~~
natmaka
s/nurture/neutralize/, indeed.

No auto-correct culprit here, that's plain and simple self-incorrect (!):
English isn't my native language.

~~~
carapace
Ah, no prob. It's funny though because the two words are almost opposite in
meaning. :-)

------
brtkdotse
Nice 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/)

------
korpiq
What programming language would cause least friction for providing discrete
types for all domain primitives or even all handled data?

~~~
thesuperbigfrog
Ada 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
Oh my god. I wish C# had this.

~~~
naasking
Ada is pretty much still the only language that has this.

~~~
rauhl
> 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.

~~~
coldacid
Well, except for the fact that it's Common Lisp. ;)

------
pintxo
> 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?

~~~
numbsafari
My 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.

~~~
Silhouette
_It sounds complicated, but if you have decent abstractions, this kind of
stuff is actually pretty easy to accomplish._

I'd be interested in seeing any end-to-end examples of how people are doing
this in practice.

For example, suppose you're maintaining a SaaS application and you have a
private key to access some third party API that certain parts of your back end
code need. How do you automate this process, so you change your private key on
a regular schedule and update all affected hosts so your application code
picks up the new one?

Ideally this needs to avoid introducing risks like a single point of failure,
a new attack surface, or the possibility of losing access to the API
altogether if something goes wrong. Assuming the old key is immediately
invalidated when you request a new one via some API, you also need a real time
way of looking up the current active key from any of your application hosts
when they need it, again without creating single points of failure, etc.

No doubt this could be done with enough work, but it doesn't feel like a
trivial problem.

~~~
infogulch
The key rotation issue where there are extra complications around
synchronizing multiple keyholders to use the updated credential is neatly
solved by having two keys, both valid, and rotating one at a time only after
all nodes that need it have moved to the new one.

~~~
Silhouette
Sure, if the API you're dealing with supports that then it's easy. But if it
doesn't, you have a non-trivial timing problem.

~~~
infogulch
I would say that an API that requires authorization keys is incomplete if it
doesn't provide the tools to manage them securely. How could a service even
offer scalability and security if it doesn't support two keys with rotation?
It's a "non-trivial problem" because consistent, available, distributed API
key rotation is not just hard, it's impossible. (See: CAP)

That doesn't solve your problem, but it means that you should take this
complaint to whatever API service offering you are using.

------
loop0
This text remembers me of some techniques used on qmail, which has a great
security record.

~~~
JdeBP
Daniel 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_mhafi...](https://hillside.net/plop/2004/papers/mhafiz1/PLoP2004_mhafiz1_0.pdf)

* [https://blog.acolyer.org/2018/01/17/some-thoughts-on-securit...](https://blog.acolyer.org/2018/01/17/some-thoughts-on-security-after-ten-years-of-qmail-1-0/)

------
thdespou
I 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.

------
tabtab
Customers 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
At 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
Nope. As a species we tend to over-prepare for the "last war", rather than
basing them on actual risk probabilities.

------
chrisdone
"Consistent at creation" is also known in the Haskell community by the phrase
"make illegal states unrepresentable".

~~~
sitkack
This 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)

------
haybanusa
Interesting 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.

------
machinecoffee
My 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.

