diff --git a/.eslint-doc-generatorrc.js b/.eslint-doc-generatorrc.js
index 6b2ce31..ca5455a 100644
--- a/.eslint-doc-generatorrc.js
+++ b/.eslint-doc-generatorrc.js
@@ -3,6 +3,7 @@ const prettierRC = require('./.prettierrc.json');
 
 /** @type {import('eslint-doc-generator').GenerateOptions} */
 const config = {
+  ignoreConfig: ['recommended-legacy'],
   postprocess: (doc) => format(doc, { ...prettierRC, parser: 'markdown' }),
 };
 
diff --git a/README.md b/README.md
index e149735..d2b77ec 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,8 @@ yarn add --dev eslint-plugin-security
 
 ## Usage
 
+### Flat config (requires eslint >= v8.23.0)
+
 Add the following to your `eslint.config.js` file:
 
 ```js
@@ -28,6 +30,16 @@ const pluginSecurity = require('eslint-plugin-security');
 module.exports = [pluginSecurity.configs.recommended];
 ```
 
+### eslintrc config (deprecated)
+
+Add the following to your `.eslintrc` file:
+
+```js
+module.exports = {
+  extends: ['plugin:security/recommended-legacy'],
+};
+```
+
 ## Developer guide
 
 - Use [GitHub pull requests](https://help.github.com/articles/using-pull-requests).
diff --git a/index.js b/index.js
index e77966f..68f2b0b 100644
--- a/index.js
+++ b/index.js
@@ -66,6 +66,14 @@ const recommended = {
   },
 };
 
-Object.assign(plugin.configs, { recommended });
+const recommendedLegacy = {
+  plugins: ['security'],
+  rules: recommended.rules,
+};
+
+Object.assign(plugin.configs, {
+  recommended,
+  'recommended-legacy': recommendedLegacy
+});
 
 module.exports = plugin;
diff --git a/test/configs/index.js b/test/configs/index.js
new file mode 100644
index 0000000..d5fb093
--- /dev/null
+++ b/test/configs/index.js
@@ -0,0 +1,16 @@
+'use strict';
+const plugin = require('../../index.js');
+const assert = require('assert').strict;
+
+describe('export plugin object', () => {
+  it('should export rules', () => {
+    assert(plugin.rules);
+    assert(typeof plugin.rules['detect-unsafe-regex'] === 'object');
+  });
+
+  it('should export configs', () => {
+    assert(plugin.configs);
+    assert(plugin.configs['recommended']);
+    assert(plugin.configs['recommended-legacy']);
+  });
+});
diff --git a/test/detect-bidi-characters.js b/test/rules/detect-bidi-characters.js
similarity index 97%
rename from test/detect-bidi-characters.js
rename to test/rules/detect-bidi-characters.js
index 9311b8b..cb3e09b 100644
--- a/test/detect-bidi-characters.js
+++ b/test/rules/detect-bidi-characters.js
@@ -4,7 +4,7 @@ const RuleTester = require('eslint').RuleTester;
 const tester = new RuleTester();
 
 const ruleName = 'detect-bidi-characters';
-const Rule = require(`../rules/${ruleName}`);
+const Rule = require(`../../rules/${ruleName}`);
 
 tester.run(ruleName, Rule, {
   valid: [
@@ -54,7 +54,7 @@ tester.run(`${ruleName} in comment-line`, Rule, {
           console.log("You are an admin.");
       /* end admins only ‮
 ⁦*/
-      /* end admins only ‮ 
+      /* end admins only ‮
  { ⁦*/
         `,
       errors: [
diff --git a/test/detect-buffer-noassert.js b/test/rules/detect-buffer-noassert.js
similarity index 95%
rename from test/detect-buffer-noassert.js
rename to test/rules/detect-buffer-noassert.js
index e9bee5e..95e387b 100644
--- a/test/detect-buffer-noassert.js
+++ b/test/rules/detect-buffer-noassert.js
@@ -4,7 +4,7 @@ const RuleTester = require('eslint').RuleTester;
 const tester = new RuleTester();
 
 const ruleName = 'detect-buffer-noassert';
-const rule = require(`../rules/${ruleName}`);
+const rule = require(`../../rules/${ruleName}`);
 
 const allMethodNames = [...rule.meta.__methodsToCheck.read, ...rule.meta.__methodsToCheck.write];
 
diff --git a/test/detect-child-process.js b/test/rules/detect-child-process.js
similarity index 98%
rename from test/detect-child-process.js
rename to test/rules/detect-child-process.js
index efc45fd..03f9b4b 100644
--- a/test/detect-child-process.js
+++ b/test/rules/detect-child-process.js
@@ -9,7 +9,7 @@ const tester = new RuleTester({
 });
 
 const ruleName = 'detect-child-process';
-const rule = require(`../rules/${ruleName}`);
+const rule = require(`../../rules/${ruleName}`);
 
 tester.run(ruleName, rule, {
   valid: [
diff --git a/test/detect-disable-mustache-escape.js b/test/rules/detect-disable-mustache-escape.js
similarity index 84%
rename from test/detect-disable-mustache-escape.js
rename to test/rules/detect-disable-mustache-escape.js
index bc3aa5a..021a49e 100644
--- a/test/detect-disable-mustache-escape.js
+++ b/test/rules/detect-disable-mustache-escape.js
@@ -5,7 +5,7 @@ const tester = new RuleTester();
 
 const ruleName = 'detect-disable-mustache-escape';
 
-tester.run(ruleName, require(`../rules/${ruleName}`), {
+tester.run(ruleName, require(`../../rules/${ruleName}`), {
   valid: [{ code: 'escapeMarkup = false' }],
   invalid: [
     {
diff --git a/test/detect-eval-with-expression.js b/test/rules/detect-eval-with-expression.js
similarity index 84%
rename from test/detect-eval-with-expression.js
rename to test/rules/detect-eval-with-expression.js
index 338a1bc..fe00156 100644
--- a/test/detect-eval-with-expression.js
+++ b/test/rules/detect-eval-with-expression.js
@@ -5,7 +5,7 @@ const tester = new RuleTester();
 
 const ruleName = 'detect-eval-with-expression';
 
-tester.run(ruleName, require(`../rules/${ruleName}`), {
+tester.run(ruleName, require(`../../rules/${ruleName}`), {
   valid: [{ code: "eval('alert()')" }],
   invalid: [
     {
diff --git a/test/detect-new-buffer.js b/test/rules/detect-new-buffer.js
similarity index 84%
rename from test/detect-new-buffer.js
rename to test/rules/detect-new-buffer.js
index 4dc1b76..6d266b9 100644
--- a/test/detect-new-buffer.js
+++ b/test/rules/detect-new-buffer.js
@@ -6,7 +6,7 @@ const tester = new RuleTester();
 const ruleName = 'detect-new-buffer';
 const invalid = 'var a = new Buffer(c)';
 
-tester.run(ruleName, require(`../rules/${ruleName}`), {
+tester.run(ruleName, require(`../../rules/${ruleName}`), {
   valid: [{ code: "var a = new Buffer('test')" }],
   invalid: [
     {
diff --git a/test/detect-no-csrf-before-method-override.js b/test/rules/detect-no-csrf-before-method-override.js
similarity index 87%
rename from test/detect-no-csrf-before-method-override.js
rename to test/rules/detect-no-csrf-before-method-override.js
index 10c0670..1b65bfa 100644
--- a/test/detect-no-csrf-before-method-override.js
+++ b/test/rules/detect-no-csrf-before-method-override.js
@@ -5,7 +5,7 @@ const tester = new RuleTester();
 
 const ruleName = 'detect-no-csrf-before-method-override';
 
-tester.run(ruleName, require(`../rules/${ruleName}`), {
+tester.run(ruleName, require(`../../rules/${ruleName}`), {
   valid: [{ code: 'express.methodOverride();express.csrf()' }],
   invalid: [
     {
diff --git a/test/detect-non-literal-fs-filename.js b/test/rules/detect-non-literal-fs-filename.js
similarity index 99%
rename from test/detect-non-literal-fs-filename.js
rename to test/rules/detect-non-literal-fs-filename.js
index 8d5bd30..560db86 100644
--- a/test/detect-non-literal-fs-filename.js
+++ b/test/rules/detect-non-literal-fs-filename.js
@@ -10,7 +10,7 @@ const tester = new RuleTester({
 
 const ruleName = 'detect-non-literal-fs-filename';
 
-tester.run(ruleName, require(`../rules/${ruleName}`), {
+tester.run(ruleName, require(`../../rules/${ruleName}`), {
   valid: [
     {
       code: `var fs = require('fs');
@@ -29,7 +29,7 @@ tester.run(ruleName, require(`../rules/${ruleName}`), {
             import { promises as fsp } from 'fs';
             import fs from 'fs';
             import path from 'path';
-            
+
             const index = await fsp.readFile(path.resolve(__dirname, './index.html'), 'utf-8');
             const key = fs.readFileSync(path.join(__dirname, './ssl.key'));
             await fsp.writeFile(path.resolve(__dirname, './sitemap.xml'), sitemap);`,
diff --git a/test/detect-non-literal-regexp.js b/test/rules/detect-non-literal-regexp.js
similarity index 89%
rename from test/detect-non-literal-regexp.js
rename to test/rules/detect-non-literal-regexp.js
index af5e054..39037c4 100644
--- a/test/detect-non-literal-regexp.js
+++ b/test/rules/detect-non-literal-regexp.js
@@ -6,7 +6,7 @@ const tester = new RuleTester();
 const ruleName = 'detect-non-literal-regexp';
 const invalid = "var a = new RegExp(c, 'i')";
 
-tester.run(ruleName, require(`../rules/${ruleName}`), {
+tester.run(ruleName, require(`../../rules/${ruleName}`), {
   valid: [
     { code: "var a = new RegExp('ab+c', 'i')" },
     {
diff --git a/test/detect-non-literal-require.js b/test/rules/detect-non-literal-require.js
similarity index 92%
rename from test/detect-non-literal-require.js
rename to test/rules/detect-non-literal-require.js
index 1d15c21..49a6e7a 100644
--- a/test/detect-non-literal-require.js
+++ b/test/rules/detect-non-literal-require.js
@@ -6,7 +6,7 @@ const tester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
 
 const ruleName = 'detect-non-literal-require';
 
-tester.run(ruleName, require(`../rules/${ruleName}`), {
+tester.run(ruleName, require(`../../rules/${ruleName}`), {
   valid: [
     { code: "var a = require('b')" },
     { code: 'var a = require(`b`)' },
diff --git a/test/detect-object-injection.js b/test/rules/detect-object-injection.js
similarity index 95%
rename from test/detect-object-injection.js
rename to test/rules/detect-object-injection.js
index 6091ac5..8867f6c 100644
--- a/test/detect-object-injection.js
+++ b/test/rules/detect-object-injection.js
@@ -5,7 +5,7 @@ const tester = new RuleTester();
 
 const ruleName = 'detect-object-injection';
 
-const Rule = require(`../rules/${ruleName}`);
+const Rule = require(`../../rules/${ruleName}`);
 
 const valid = 'var a = {};';
 // const invalidVariable = "TODO";
diff --git a/test/detect-possible-timing-attacks.js b/test/rules/detect-possible-timing-attacks.js
similarity index 94%
rename from test/detect-possible-timing-attacks.js
rename to test/rules/detect-possible-timing-attacks.js
index 34a6136..9b8c9ac 100644
--- a/test/detect-possible-timing-attacks.js
+++ b/test/rules/detect-possible-timing-attacks.js
@@ -4,7 +4,7 @@ const RuleTester = require('eslint').RuleTester;
 const tester = new RuleTester();
 
 const ruleName = 'detect-possible-timing-attacks';
-const Rule = require(`../rules/${ruleName}`);
+const Rule = require(`../../rules/${ruleName}`);
 
 const valid = 'if (age === 5) {}';
 const invalidLeft = "if (password === 'mypass') {}";
diff --git a/test/detect-pseudoRandomBytes.js b/test/rules/detect-pseudoRandomBytes.js
similarity index 87%
rename from test/detect-pseudoRandomBytes.js
rename to test/rules/detect-pseudoRandomBytes.js
index 0162a46..245aeec 100644
--- a/test/detect-pseudoRandomBytes.js
+++ b/test/rules/detect-pseudoRandomBytes.js
@@ -6,7 +6,7 @@ const tester = new RuleTester();
 const ruleName = 'detect-pseudoRandomBytes';
 const invalid = 'crypto.pseudoRandomBytes';
 
-tester.run(ruleName, require(`../rules/${ruleName}`), {
+tester.run(ruleName, require(`../../rules/${ruleName}`), {
   valid: [{ code: 'crypto.randomBytes' }],
   invalid: [
     {
diff --git a/test/detect-unsafe-regexp.js b/test/rules/detect-unsafe-regexp.js
similarity index 92%
rename from test/detect-unsafe-regexp.js
rename to test/rules/detect-unsafe-regexp.js
index 077a355..09095fd 100644
--- a/test/detect-unsafe-regexp.js
+++ b/test/rules/detect-unsafe-regexp.js
@@ -4,7 +4,7 @@ const RuleTester = require('eslint').RuleTester;
 const tester = new RuleTester();
 
 const ruleName = 'detect-unsafe-regex';
-const Rule = require(`../rules/${ruleName}`);
+const Rule = require(`../../rules/${ruleName}`);
 
 tester.run(ruleName, Rule, {
   valid: [{ code: '/^d+1337d+$/' }],