{"id":16024,"date":"2023-02-08T18:35:43","date_gmt":"2023-02-08T18:35:43","guid":{"rendered":"https:\/\/slack.engineering\/?p=16024"},"modified":"2023-02-08T20:48:49","modified_gmt":"2023-02-08T20:48:49","slug":"hakana-taking-hack-seriously","status":"publish","type":"post","link":"https:\/\/slack.engineering\/hakana-taking-hack-seriously\/","title":{"rendered":"Hakana: Taking Hack Seriously"},"content":{"rendered":"<p>TL; DR: We\u2019re announcing a new open source type checker for Hack, called <a href=\"https:\/\/github.com\/slackhq\/hakana\">Hakana<\/a>.<\/p>\n<hr \/>\n<p>Slack launched in 2014, built with a lot of love and also a lot of PHP code.<\/p>\n<p>We started migrating to a different language called Hack <a href=\"https:\/\/slack.engineering\/taking-php-seriously\/\">in 2016<\/a>. Hack was created by Facebook after they had struggled to scale their operations with PHP. It offered more type-safety than PHP, and it came with an interpreter (called HHVM) that could run PHP code faster than PHP\u2019s own interpreter.<\/p>\n<p>Much has changed in PHP-land since we switched. PHP is faster than it used to be, and it has borrowed a number of Hack features (such as <a href=\"https:\/\/wiki.php.net\/rfc\/constructor_promotion\">constructor property promotion<\/a>). A great deal of the PHP community has also embraced type checking \u2014 there are now some great third-party type checkers to choose from.<\/p>\n<p><a href=\"https:\/\/slack.engineering\/hacklang-at-slack-a-better-php\/\">Sticking with Hack<\/a> has given us access to additional runtime speed boosts, performance-enhancing language constructs like `async`, and a typechecker that\u2019s more strict by default than PHP typecheckers. But we\u2019ve missed out on features provided by PHP typecheckers, including the ability to customize type inference rules to find issues specific to our codebase and automated security vulnerability detection.<\/p>\n<p>Slack has hundreds of developers writing Hack. We want to give them the best possible experience, so last year we started building a type checker that could fill those gaps.<\/p>\n<p>We\u2019ve dubbed that static analysis tool <b>Hakana<\/b>, and it\u2019s <a href=\"https:\/\/github.com\/slackhq\/hakana\">now available on GitHub<\/a>!<\/p>\n<p>Hakana is based on <a href=\"https:\/\/psalm.dev\/\">Psalm<\/a>, an open-source PHP static analysis tool I created, and it\u2019s written in Rust. Hakana re-uses a Hack parser that\u2019s bundled with the Hack interpreter.<\/p>\n<p>A bonus of writing it in Rust: with a bit of prodding, Hakana can run just about anywhere. For example, it runs <a href=\"https:\/\/hakana.dev\/\">in your web browser<\/a> via WASM.<\/p>\n<h2>How we use Hakana<\/h2>\n<p>At Slack we run Hakana in CI to enforce good code behavior in a range of areas. Here\u2019s an incomplete list:<\/p>\n<ul>\n<li>It prevents unused functions and unused private methods.<\/li>\n<li>It prevents unused assignments inside closures.<\/li>\n<li>It detects both impossible and redundant type-checks.<\/li>\n<li>It warns us about potential SQL-injection attacks and cross-site scripting vulnerabilities (more on this below).<\/li>\n<li>It prevents misuse of internal Slack APIs (via plugin hooks).<\/li>\n<\/ul>\n<p>We also use Hakana to automate type-aware API migrations (again via plugin hooks) and to delete unused functions in bulk. Thanks to Rust, those whole-codebase migrations are relatively quick.<\/p>\n<h2>Security<\/h2>\n<p>PHP makes it really easy to make a dynamically-rendered website. PHP also makes it really easy to create an utterly insecure dynamically-rendered website.<\/p>\n<p>Hack improves on this slightly, by supporting a system for generating HTML output called XHP. XHP is secure-by-default against cross-site scripting attacks, but it doesn\u2019t stop you from leaking customer data, and Hack doesn\u2019t prevent you from shooting yourself in the foot with a wide range of other security vulnerabilities.<\/p>\n<p>For a host of reasons (including compliance obligations) Slack needed a tool that could discover those vulnerabilities. Psalm, the type checker that Hakana is based on, already does security analysis, so it was relatively simple to add security analysis to Hakana as well.<\/p>\n<p>Hakana isn\u2019t the first security analysis tool for Hack \u2014 for years, Facebook has been using an internal, closed-source tool <a href=\"https:\/\/engineering.fb.com\/2019\/08\/15\/security\/zoncolan\/\">called Zoncolan<\/a> \u2014 but Hakana is the first that everyone can use.<\/p>\n<p>Hakana works in much the same way as Zoncolan. It examines how data can flow between different functions in a codebase, and checks if attacker-controlled data can show up in places it shouldn\u2019t.<\/p>\n<p>To date, Hakana has found a number of exploitable vulnerabilities in production code at Slack (that were immediately fixed, <i>and<\/i> we checked our logs to ensure that the vulnerabilities had not <i>actually<\/i> ever been exploited).<\/p>\n<h2>Security in the type system<\/h2>\n<p>Hakana\u2019s security analysis mode is a form of interprocedural analysis \u2014 it looks at the way data flows between functions. Hakana also supports detecting one type of vulnerability (SQL injection) via intraprocedural analysis, just by examining types at function boundaries.<\/p>\n<p>To do this, Hakana borrows the concept of literal string types <a href=\"https:\/\/psalm.dev\/docs\/annotating_code\/type_syntax\/scalar_types\/#literal-string\">from Psalm<\/a>. In Hack code we can define a type alias:<\/p>\n<pre><code class=\"language-php\">&lt;&lt;Hakana\\SpecialTypes\\LiteralString()&gt;&gt;\ntype db_query_string = string;<\/code><code>\n<\/code><\/pre>\n<p>Though the official Hack typechecker just treats this as a string, Hakana treats it as a special type `literal-string`, a subtype of string that can only be concatenated or interpolated with other literal-strings. Passing a string into a function that expects a literal-string causes Hakana to emit an error:<\/p>\n<pre><code class=\"language-php\">function get_id_query(string $id): db_query_string {\n    return &quot;select * from users where id = &#039;$id&#039;&quot;;\n    \/\/ Error: The type `string` is more general\n    \/\/ than the declared return type `literal-string`\n}<\/code><\/pre>\n<h2>Extending Hakana<\/h2>\n<p>PHP static analysis tools are generally highly-customisable. Customisable static analysis is really useful for PHP, because its interpreter allows lots of tricks (e.g. <a href=\"https:\/\/www.php.net\/manual\/en\/language.oop5.magic.php\">magic methods<\/a>) that can confound one-size-fits-all static analysis tools.<\/p>\n<p>Most of those tricks don\u2019t work in Hack code, so there\u2019s far less of a need for customisable static analysis. Even so, we\u2019ve found a lot of value in extending Hakana with plugins.<\/p>\n<p>For example, we use a custom plugin to tell Hakana that a method call on our internal <code>Result<\/code> object, <code>$some_result-&gt;is_ok()<\/code>, is equivalent to the more verbose <code>$some_result is ResultSuccess&lt;_&gt;<\/code> typecheck.<\/p>\n<p>We also use custom plugins to perform type-aware migrations at speed across the entire codebase.<\/p>\n<p>Building a plugin system for Rust is not simple \u2014 our custom version of Hakana wraps the open-source core as a library, using its plugin hooks where necessary \u2014 but it\u2019s an important feature for us.<\/p>\n<h2>Speed<\/h2>\n<p>Hakana was adapted from Psalm, a type checker written in PHP. While PHP is fast for an interpreted language, it can\u2019t compete with a compiled tool. To analyze a codebase of Slack\u2019s size \u2014 many millions of lines of code \u2014 we needed a tool that\u2019s as fast as possible.<\/p>\n<p>Hakana, written in Rust, <b>runs about 5x faster<\/b> than the PHP-based tool on which it\u2019s modeled.<\/p>\n<p>We haven\u2019t spent much time tuning performance, but when analyzing the full Slack codebase (about 5 million lines of code) performance is on par with the official Hack typechecker. That\u2019s good enough for us, for now.<\/p>\n<h2>Why open-source Hakana?<\/h2>\n<p>Hack sort of looks like PHP but with more types. In that respect it\u2019s been compared to TypeScript. But unlike TypeScript, which compiles down to JavaScript, Hack requires its users to change a lot of their server infrastructure too.<\/p>\n<p>Due to that high switching cost, today Hack is only used at a few companies. It\u2019s possible that nobody else aside from Slack will have a reason to use Hakana.<\/p>\n<p>Even so, there are a few reasons why we think it\u2019s worth open-sourcing:<\/p>\n<ul>\n<li>\ud83d\udd0d The broader programming language community may have useful input \u2014 especially when it comes to security analysis.<\/li>\n<li>\ud83e\udd1d Psalm, another open-source tool, was the basis for Hakana. By open-sourcing our own tool we\u2019re returning the favor.<\/li>\n<li>\ud83c\udfe2 Though it wouldn\u2019t be easy, companies with extremely large PHP codebases might consider forking Hakana and altering it to analyze PHP code.<\/li>\n<\/ul>\n<h2>Conclusion<\/h2>\n<p>Slack is an indispensable tool for millions around the world. Hack is now an integral part of Slack, and so we\u2019ve created a tool that we hope will become indispensable to our Hack developers. <a href=\"https:\/\/github.com\/slackhq\/hakana\">We\u2019re open-sourcing it today<\/a> with the hope that others will find it useful and interesting.<\/p>\n","protected":false},"excerpt":{"rendered":"TL; DR: We\u2019re announcing a new open source type checker for Hack, called Hakana. Slack launched in 2014, built with a lot of love and also a lot of PHP code. We started migrating to a different language called Hack in 2016. Hack was created by Facebook after they had struggled to scale their operations&hellip;","protected":false},"author":326,"featured_media":16106,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[],"tags":[572],"class_list":{"0":"post-16024","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"tag-hacklang","8":"ts-entry"},"acf":{"subtitle":"","author_group":{"configure_author":"wordpress","authors":[{"ID":16025,"post_author":"326","post_date":"2023-01-24 22:09:09","post_date_gmt":"2023-01-24 22:09:09","post_content":"","post_title":"Matt Brown","post_excerpt":"","post_status":"publish","comment_status":"closed","ping_status":"closed","post_password":"","post_name":"16025","to_ping":"","pinged":"","post_modified":"2023-01-24 22:10:18","post_modified_gmt":"2023-01-24 22:10:18","post_content_filtered":"","post_parent":0,"guid":"https:\/\/slack.engineering\/?post_type=author&#038;p=16025","menu_order":0,"post_type":"author","post_mime_type":"","comment_count":"0","filter":"raw"}],"custom_author":""},"series":false,"tags":[572]},"jetpack_featured_media_url":"https:\/\/slack.engineering\/wp-content\/uploads\/sites\/7\/2023\/01\/Slice2.jpg","_links":{"self":[{"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/posts\/16024","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/users\/326"}],"replies":[{"embeddable":true,"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/comments?post=16024"}],"version-history":[{"count":12,"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/posts\/16024\/revisions"}],"predecessor-version":[{"id":16117,"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/posts\/16024\/revisions\/16117"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/media\/16106"}],"wp:attachment":[{"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/media?parent=16024"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/categories?post=16024"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/slack.engineering\/wp-json\/wp\/v2\/tags?post=16024"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}