Skip to main content

TypeScript is Now Available as a Policy Language

· 7 min read
pizzacat83

We're excited to announce that in addition to the Rego language, TypeScript is now available as a language for defining audit rules on Shisho Cloud. This enables you to create custom audit rules tailored to your organization's policies using a familiar language. Please note that managed audit rules by Flatt Security is currently available only in Rego. However, we are actively working on providing TypeScript versions in the near future.

eyecatch

Overview

With this release, you can now use TypeScript (and JavaScript) as a language for describing audit rules that are continuously run on Shisho Cloud.

Below is an example of an audit rule written in TypeScript. This checks if any network firewall on Google Cloud allows SSH connections on port 22 from any IPv4 address (0.0.0.0/0).

decide.ts
function decide(input: Input): Decision[] {
return input.googleCloud.projects.map(project =>
project.network.vpcNetworks.map(network => {
const insecure_rules = network.firewallRules.filter(allows_ssh_public_access);
const ok = insecure_rules.length === 0;
const decision: Decision = {
header: {
api_version: "decision.api.shisho.dev/v1beta",
kind: "network_ssh_access",
subject: network.metadata.id,
type: ok ? TYPE_ALLOW : TYPE_DENY,
severity: ok ? SEVERITY_INFO : SEVERITY_HIGH,
},
}
return decision;
})).flat();
}

function allows_ssh_public_access(rule: FirewallRule): boolean {
const SSH_PORT = 22;
return rule.direction === "INGRESS" &&
rule.sourceRanges.some(range => range === "0.0.0.0/0") &&
rule.allowed.some(({ ipProtocol, ports }) =>
(ipProtocol === "all" || ipProtocol === "tcp" || ipProtocol === "sctp") &&
ports.some(({ from_, to }) => from_ <= SSH_PORT && SSH_PORT <= to)
)
}

For those familiar with TypeScript, understanding the logic of this audit rule should not be difficult. The rule enumerates insecure firewall rules (insecure_rules) based on the condition allows_ssh_public_access. It then creates an object decision representing the audit result of each network. The function allows_ssh_public_access, which determines whether a firewall rule is problematic, checks whether the source IP range includes 0.0.0.0/0, whether the port range includes 22, etc.

Customizing audit rules is also straightforward. As an example, instead of using a deny-list method that disallows 0.0.0.0/0 to be the source IP range, suppose we would like to use an allow-list method that only allows 192.0.2.0/24 to be the source IP range for port 22. To change the audit rule above to the allow-list method, simply rewrite the code as follows:

   return rule.direction === "INGRESS" &&
- rule.sourceRanges.some(range => range === "0.0.0.0/0") &&
+ rule.sourceRanges.some(range => range !== "192.0.2.0/24") &&
rule.allowed.some(({ ipProtocol, ports }) =>

This is just one example of what you can customize. For example, auditing ports other than 22 is also easily achievable. Since the logic of audit rules is expressed as TypeScript code, you can customize every part of the logic freely.

Besides the familiarity among engineers, the key feature of TypeScript is its type system. While type systems often bring various benefits to ordinary software development, many of these benefits are also applicable to the implementation of audit rules for Shisho Cloud.

Types often serve as documentation of the structure of the input/output data. From this perspective, audit rules in Shisho Cloud are roughly functions that take input data (Input) and return an array of audit results (Decision[]). The type definition of the input data, which is automatically generated by the Shisho Cloud CLI, looks like this:

input.gen.ts
export type Input = {
/** All data from Google Cloud integration */
googleCloud: {
/** Projects in Google Cloud */
projects: Array<{
/** The unique, user-assigned ID of the project. It must be 6 to 30 lowercase letters, digits, or hyphens. */
id: string,
/** The network configuration of the project */
network: {
/** All VPC networks */
vpcNetworks: Array<{
/** The metadata to identify this resource in Shisho Cloud */
metadata: {
/** The ID to identify the report in Shisho Cloud */
id: ResourceID,
},
/** All firewall rules */
firewallRules: Array<{
/** The list of ALLOW rules specified by this firewall */
allowed: Array<{
/** The IP protocol allows */
ipProtocol: string,
/** The list of ports to allow; if empty, all ports are allowed */
ports: Array<{
/** The first port in the range */
from_: Int,
/** The last port in the range */
to: Int,
}>,
// ...

This type definition includes not only the types themselves but also documentation comments, making it easy to understand what kind of data the audit rule receives.

The type information is also used by editors' language support. For example, when writing code that constructs a list of audit results from the input data, property names are autocompleted in the editor based on type information. This makes it easy to straightforwardly navigate to the field you want to inspect.
Completion of property names provided by the language support

Of course, the immediate detection of type errors is another great benefit of the type system. If you mistake the type of an expression or make a typo in a property name, the editor instantly highlights the mistake.
Mismatched type leads to an immediate type error in the editor

Moreover, you can use type checking to ensure all possible structures of the input data are handled. For example, in the Stateful Ruleset of AWS Network Firewall, you can specify actions such as "pass", "drop", "reject", or "alert" for packets. However, the following code defines audit results for only three of them. Nevertheless, since we have included an exhaustiveness check in the default clause of the switch statement, the type checker points out that the handling of the "alert" case is missing. Envisioning various possible states of a resource helps you define audit rules with wider coverage. And as we saw, type information is a powerful aid in considering all situations.

To sum up, the type system is a great help in writing audit rules with fewer flaws. With this assistance, those who are familiar with TypeScript will be able to write audit rules with much more confidence than to write rules in Rego.

Of course, not all bugs can be discovered through type checking alone. Therefore, validating the behavior of audit rules through methods like unit testing is also valuable. Tests for audit rules can be written in the same way as for ordinary TypeScript code.

decide.test.ts
import { assertEquals } from "https://deno.land/std@0.202.0/assert/assert_equals.ts"
import { TYPE_DENY } from "https://deno.land/x/shisho_cloud_policy_helpers@v0.0.1/decision/mod.ts"
import { decide } from "./decide.ts"
import { Input } from "./input.gen.ts"

Deno.test("Denies networks that allow public access to port 22", () => {
const input: Input = {
googleCloud: {
projects: [{
id: "project-foo",
network: {
vpcNetworks: [{
metadata: { id: "googlecloud-nw-vpc-network|00000000|00000000" },
firewallRules: [{
allowed: [{
ipProtocol: "tcp",
ports: [{ from_: 0, to: 65535 }],
}],
direction: "INGRESS",
sourceRanges: ["0.0.0.0/0"],
}],
}],
},
}],
},
};

const decisions = decide(input);

assertEquals(decisions.length, 1);
assertEquals(decisions[0].header.type, TYPE_DENY);
})
$ deno test decide.ts
Check file:///path/to/decide_test.ts
running 1 test from ./decide_test.ts
Denies networks that allow public access to port 22 ... ok (0ms)

ok | 1 passed | 0 failed (1ms)

Getting Started

Try it with Shisho Cloud for free

Thank you for your interest! Please contact us through the Shisho Cloud official page. We will promptly guide you on how to start with the Shisho Cloud free trial.

Try it with an existing Shisho Cloud organization

Please contact Flatt Security Support, and we will provide documents on how to write audit rules in TypeScript.

Please note that managed audit rules by Flatt Security is currently available only in Rego. However, we are actively working on providing TypeScript versions in the near future.

info

Some parts of the GraphQL queries included in the previous managed audit rules have been slightly modified, so that the queries can be shared with TypeScript rules provided in the future. However, this change generally does not affect the behavior of existing audit rules written in Rego.

When implementing custom audit rules in TypeScript based on the GraphQL queries used by managed audit rules, using outdated GraphQL queries may result in code generation failure for TypeScript. In such cases, please use the latest GraphQL queries from the repository of managed audit rules.