What shall we make of the Zen of Python? Is it the epos of the language? A philosophy of computing? There are those days when one wonders whether the Python language itself is an elaborate prank that has gotten way out of hand. It is both beautiful and irreverent. What more can we expect of a scripting kit named after a bold comedy troupe?
The Zen of Python's source code found here (and copied below) is a string scrambled with Caesar’s cipher returned from a one-line iterator over an ASCII dictionary. Javier Buzzi pointed out in a comment that it also happens to be seven times slower than using a translation function. Hyeshik Chang replaced the horror with str.decode('rot_13') and put it back like it was on the same day.
The Zen of Python
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Well, the joke is on all of us because Ruby and Go and pretty much every growing 21st-century language paid homage to the Zen of Python. To keep it going, let's apply the Zen of Python to application security. Without being facetious, we suppose that the principles constitute a set of excellent secure coding principles.
Four categories group together the nineteen principles from the Zen of Python. As developers, we must enhance our code with these four inalienable properties. Our code should be readable, explicit, simple, and singular. Such code is enjoyable to maintain. A code reviewer has an easier time spotting potential vulnerabilities. It yields to testing without much refactoring. And, when a problem is discovered, the fix is just a small patch and a few additional unit tests.
- Beautiful is better than ugly.
- Readability counts.
- Flat is better than nested.
- Sparse is better than dense.
Each class and function should be obvious and as self-documenting as possible. On average, an engineer holds six to eleven entities in their head when reading code. If your function has five parameters, plus their five types, the available slots left shrink to as low as one. In other words, they will forget most of the parameters and their types by the time they read halfway through your function.
If we approach application security as an attention-deficit management problem at the very core, entities that consume attention become public enemy number one. Furthermore, we should think of code as a conversation with our teammates more than an hardware instruction set. When others read our code, they enter into an exchange of ideas with us. If we fail to communicate our intent clearly, we set them up for a security breach.
Likewise, readable code which contains a vulnerability makes its presence more obvious to others, especially in code review. Would not all of us love to catch 100% of software vulnerabilities in a code review? That chance of discovery is the least costly to the company and most sabotaged by code smell. The reviewer's special attention will be consumed by code smell first!
Security takeaway: Readable code is more secure code, as it takes fewer engineer brain cycles to process and causes vulnerabilities to jump off the screen.
- Explicit is better than implicit.
- Complex is better than complicated.
- In the face of ambiguity, refuse the temptation to guess.
- Errors should never pass silently. Unless explicitly silenced.
Readable code is easier to perceive. Explicit code is easier to comprehend and is mainly self-documenting. It leaves little room for assumptions or guesses. It scopes functionality by logical relations that are easy to grasp.
What makes code explicit? Two things: (1) specificity of syntax and (2) sensible grouping and layering of functionality. To put it in simplest terms: pick lucid names for variables and functions, use type annotations liberally, silo related instructions together, and do not silence errors.
The aim is to minimize the degree of abstraction between a real entity and its model. The more obviously the code corresponds to modeled business logic, the more explicit it is.
What does this have to do with security? Two answers correspond to the two properties of explicit code noted above.
First, a well-designed model does not only model world behavior excellently but also seeks to represent and anticipate every possible kind of misbehavior. Most code vulnerabilities are born in the realm of unexpected or unintended interactions which typically surface during execution. An explicit coder purposefully minimizes unexpected and unintended conditions by offsetting them with terse documentation, refactoring, specific failure-state exceptions, and occasional comments. Explicit code radiates a light that leaves less room for the dark corner cases.
Second, once we get past syntactical alterations, implicit code becomes explicit via refactoring! Code that is easy to test with interfaces that are easy to mock is de-facto explicit. For example, when a complicated function is broken up into two, the new functions obtain more accurate names, and their parameters become self-documenting by gaining visibility and narrower scope. This transition is what is meant by the phrase "complex is better than complicated."
Is down-factored code automatically more secure? The quality of the test suite will ultimately answer that question. However, a test suite never reaches sufficient quality when facing implicit instructions. It cannot make up for that deficiency. Thus, writing explicit code is a necessary condition for creating secure code.
Security takeaway: Explicit code models real-world behavior and misbehavior, making it easy to mock and test.
- Simple is better than complex.
- If the implementation is hard to explain, it's a bad idea.
- If the implementation is easy to explain, it may be a good idea.
- Now is better than never. Although never is often better than right now.
- Namespaces are one honking great idea—let's do more of those!
The bias of simplicity favors solutions with fewer steps and moving parts. Simple code happens at the package and namespace level. This differs from readability and explicitness, which are achieved by working with functions.
Simplicity should dictate our project layout, choice of protocols and interfaces, and selection of dependencies. Less is more. Some classes should become functions, some packages classes, and some dependencies packages.
The escalation of supply chain attacks against Python is not accidental. Similar to NodeJS, the number of dependencies in Python projects has ballooned in recent years. Each additional inclusion bears a security trade-off.
We may argue whether this is avoidable as market competition squirts gasoline on the feature bloat pyre. The Pythonic solution to this is to focus your efforts on designing a simpler project backbone, which is more extensible than complex. Focus security efforts on interfaces that drive extensions. Build security in from the very start. Back it up with a robust test suite.
A simple and extensible project is easier to secure because high-priority targets become few and obvious: interfaces, drivers, and gateways. Then, even if a dependency breach occurs, the damage is mitigated by the hardening of your extension points.
Security takeaway: Simpler code is more secure.
- Special cases aren't special enough to break the rules.
- There should be one — and preferably only one — obvious way to do it.
- Although practicality beats purity.
The concept of singularity is easier to grasp. Do not repeat yourself. Special cases are not special enough. If there are many special cases, group and sequester them by some commonality until they no longer appear special.
Singular is secure: less castle wall to cover for your archers. Threat modeling becomes manageable. Singularity makes defense-in-depth possible because the threat comes from a predictablesource or direction.
Security takeaway: Singularity reduces attack surface.
View the nineteen elements of the Zen of Python as four foundational secure code qualities: readable, explicit, simple, and singular. Incidentally, code written this way is also pleasant to update. It is a delight to be a part of a team that builds security idiomatically from the beginning. Work towards this goal for your projects and your team: create security from the start and shift security left.
Radomir Dopieralski and many others quipped that the Zen of Python is a farce. Perhaps. On the other hand, it is memorable. Those who take it as a proverb rather than a commandment will find a grain of transcendent wisdom in it. As far as we can tell, it conveys potent security principles.
P.S. If you would like to learn how to put those security principles into practice, Security Journey has just released Green Belt level security training for Python. Schedule a demo.