A custom ESLint plugin for fully validating Firebase Security Rules (.rules files).
Powered directly by the authoritative ANTLR4 FirebaseRulesParser grammar used by Firebase internally, this plugin natively catches syntax errors identically to real deployment pipelines while utilizing custom rule checks for semantic issues.
Install the plugin:
npm install @firebase/eslint-plugin-security-rules --save-devAdd the plugin to your eslint.config.js or .eslintrc configuration. You must map .rules files to use the plugin's custom parser so ESLint correctly applies the syntax validation.
Flat Config (eslint.config.js) - Recommended Setup:
import firebaseRulesPlugin from '@firebase/eslint-plugin-security-rules';
export default [
{
...
},
firebaseRulesPlugin.configs['flat/recommended'] // Apply recommended rules
];ESLint v8 Setup
module.exports = {
// Other base configs for their JS/TS files
extends: ['eslint:recommended'],
overrides: [
{
// 1. Target the specific rules files
files: ['*.rules'],
// 2. Point to your custom parser so ESLint knows how to read the file
parser: '@firebase/eslint-plugin-security-rules/parser',
// 3. Extend your legacy config using the magic string format
extends: ['plugin:@firebase/security-rules/recommended'],
}
]
};Flat Config (eslint.config.js) - Manual/Explicit Setup:
import firebaseRulesPlugin from '@firebase/eslint-plugin-security-rules';
export default [
{
files: ["**/*.rules"],
plugins: {
"@firebase/security-rules": firebaseRulesPlugin
},
languageOptions: {
parser: firebaseRulesPlugin.parser,
},
rules: {
"@firebase/security-rules/no-open-reads": "warn",
"@firebase/security-rules/no-open-writes": "error",
"@firebase/security-rules/no-redundant-matches": "error"
}
}
];Run ESLint normally, ensuring it checks .rules extensions:
npx eslint firebase.rulesCurrently active rules:
no-open-reads: Warns whenallow read:is open to the public (if true).no-open-writes: Errors whenallow write:is open to the public (if true).no-redundant-matches: Catches logically redundantmatchdefinitions in identical scopes.
New rules can easily integrate by interacting with the ANTLR4 parse tree exposed by the parser wrapper.
- Add your new rule logic in
src/rules/your-rule-name.ts. - Add your tests in
tests/src/rules/your-rule-name.test.ts. - Export the new rule inside
src/index.ts.
This plugin creates a custom Program wrapper for the ESLint standard AST and passes the canonical ANTLR grammar validation objects into the context services via services.tree.
A rudimentary rule template extracting contexts looks like:
export default {
meta: {
type: "suggestion",
docs: {
description: "disallow something",
category: "Best Practices"
},
schema: []
},
create(context) {
return {
"Program"(node) {
const sourceCode = context.sourceCode || context.getSourceCode();
const services = context.parserServices || sourceCode.parserServices || {};
if (!services.tree) return; // ensure tree loaded
// Helper to walk the ANTLR node structure
function walk(antlrNode, callback) {
if (!antlrNode) return;
callback(antlrNode);
if (antlrNode.children) {
for (let child of antlrNode.children) {
walk(child, callback);
}
}
}
walk(services.tree, (antlrNode) => {
// Search for distinct Rule contexts, mapped via the ANTLR grammar definitions
// e.g., "MatchRuleDeclarationContext", "PermissionDeclarationContext"
if (antlrNode.constructor && antlrNode.constructor.name === "PermissionDeclarationContext") {
// Implement checking logic...
// To trigger an error:
// context.report({
// node: node,
// message: "Custom defined lint error message."
// });
}
});
}
};
}
};The test suite interacts perfectly natively with RuleTester. Since the plugin is written in TypeScript, run the build step before testing.
npm test