
Security News
/Research
Wallet-Draining npm Package Impersonates Nodemailer to Hijack Crypto Transactions
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
Compiler for an alternative YAML-based syntax for Firebase security rules.
Compiler for an alternative YAML-based syntax for Firebase security rules. The new syntax is much more readable but retains the overall semantics of the traditional JSON format, so there's no surprises.
First, install the compiler:
npm install -g fireplan
Then create a rules.yaml
file like this:
functions:
- percentage: number && next >= 0 && next <= 100
- canUpdate(subject): root.users[auth.uid].permissions[subject].write
root:
data:
$subject:
.read: true
.write: canUpdate($subject)
value: required percentage
description: string
users:
$uid:
.read/write: auth.uid == $uid
.ref: user
role: required oneOf('visitor', 'user', 'admin')
permissions:
$subject:
.write: user.role == 'user' || user.role == 'admin'
write: required boolean
Then compile it into rules.json
like so:
fireplan rules.yaml
Fireplan security rules are written in YAML, which gets translated to JSON by the compiler. Indentation indicates the hierarchical structure and there's no need for quotes, but otherwise it's pretty similar to the traditional syntax.
One simple up-front difference: the root of the rule hierarchy is root:
rather than "rules":
, to better match the predefined root
variable in security expressions.
Security expressions are used in .read
, .write
and .value
rules, as well as in function definitions (explained below). All traditional security expression are valid in Fireplan as well, but there's a few extra features you can take advantage of:
data.child('foo').child($bar)
becomes data.foo[$bar]
..val()
calls altogether, as they'll be inferred automatically (unless you're calling a String
method like length
or contains()
, then you must keep the .val()
)..ref: <name>
, then use these references in your expression. They'll be automatically transformed to newData.parent().parent()...
so you can easily reference other parts of the new object in your rules. (In .read
expressions data
will be used instead.)Putting all these together, an expression like:
newData.child('counter').val() == data.child('counter').val() + 1
becomes:
next.counter == prev.counter + 1
The three basic kinds of rules are .read
, .write
and .value
, corresponding directly to the original .read
, .write
and .validate
. There's also a couple bits of syntactic sugar:
.read/write
rule if the .read
and .write
expressions are the same. This is particularly useful for properties that will be updated transactionally, since transaction()
requires both read and write access to its data.foo: auth.uid == 'admin'
is the same as foo: {.value: auth.uid == 'admin'}
orfoo:
.value: auth.uid == 'admin'
A very common validation need is to check whether a property has the expected children. You can do this manually using hasChildren()
and $other: false
catchalls, but Fireplan has a special syntax that makes it much easier. By default, any child listed under a property is optional but you can make it required by starting its value constraint with the keyword required
. Normally no children other than the required and optional ones listed are allowed, but if you'd like to accept any others as well (with no further validation) you can add .more: true
to the property.
To make some Firebase queries run efficiently you also need to earmark some children for indexing. You can do this by adding the keyword indexed
to a child's value constraint. (If both required
and indexed
are used together, they can come in any order but must precede any other constraint code.) All children marked as indexed
will be collected into the Firebase .indexOn
property of the parent of their nearest wildcard ($keyCapture
) ancestor, creating "deep" indexes automatically as necessary.
Putting it all together looks like this:
root:
foo:
bar: required string
baz:
.value: required
qux: number
.more: true
$stuff:
name: required indexed string
This means that foo
is optional, but if written it must have children bar
(a string) and baz
, and no others. In turn, baz
can have any children at all, but if qux
is specified then it must be a number. Other children of root ($stuff
) need to have a name
property (a string), and will be indexed on it.
As security rules grow more complex, you may find yourself repeatedly writing out the same expression snippet in various contexts. To cut down on duplication, Fireplan allows you to define functions that can then be "called" from expressions (including other functions). The definitions go into a top-level functions:
block like this:
functions:
- foo(bar, baz): next.qux == bar || auth.uid == baz
- foo2: foo('arrr', 'matey')
A function can take any number of arguments; if it doesn't take any, you can leave out the empty parentheses. Function names must be unique (there's no dispatch on the number of arguments). A function's body is an expression just like that of any security rule, and can access the function's arguments as well as the usual security rules globals (auth
, next
, etc.).
Functions are called in the usual way, like foo('bar', next.baz)
. A function can call other functions in its body but recursion is forbidden (and will crash the compiler). If a function doesn't take arguments you can also call it without parentheses, like foo2
. This is especially convenient for defining new "value types", like percentage
in the example at the top.
Fireplan predefines three value types string
, boolean
and number
like so:
functions:
- string: next.isString()
- boolean: next.isBoolean()
- number: next.isNumber()
- any: true # also implies .more: true for this child
There's also a special predefined function oneOf
that is used to constrain a property to one of a list of values (typically strings). Use it like this (and prefix with required
to taste):
root:
foo: oneOf('bar', 'baz', 'qux')
Finally, for object types, you can apply YAML's referencing mechanism to reuse a definition in multiple places:
root:
foo: &fooType # establish a reference called fooType
bar: string
baz: required number
qux: *fooType # dereference fooType
# bar and baz are filled in here automatically
If you want, you can set up a separate types:
hierarchy and define type references there—Fireplan doesn't care if you have extra top-level keys.
Fireplan makes available a special env
variable that lets you substitute environment variable values at compile time. For example, if you need to distinguish between the development and production datastores in your rules, you could set export DATASTORE=dev
or export DATASTORE=prod
in your build, then check for it in your rules like so:
root:
.write: env.DATASTORE == 'dev'
Fireplan supports Firecrypt encryption annotations.
You can prefix a .value
rule with the keyword encrypted
(mixed in any order with required
and indexed
), or suffix a key with /encrypted
, to indicate that that value or key needs to be encrypted. You can additionally include a pattern after the keyword to indicate which parts of the key or string value should be encrypted, like encrypted[#-#-.]
, where #
indicates an encrypted chunk and .
an unencrypted one.
You can also suffix a $
wildcard key with /few
to indicate that you don't expect there to be a lot of children there, and that it's safe to try to load all of them at once. Judicious application of this annotation can greatly speed up bulk encryption / key rotation operations in Firecrypt.
If any encrypted
or few
annotations are present, Fireplan will emit a rules_firecrypt.json
file that you can then feed into Firecrypt and related tools.
Please let me know if you have any problems.
FAQs
Compiler for an alternative YAML-based syntax for Firebase security rules.
The npm package fireplan receives a total of 3 weekly downloads. As such, fireplan popularity was classified as not popular.
We found that fireplan demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
/Research
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.