diff --git a/doc/requirements.txt b/doc/requirements.txt
index 0d2074715..c8f46ece5 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -4,3 +4,7 @@
sphinx>=4.0.0 # BSD
sphinx-rtd-theme>=0.3.0
sphinx-copybutton
+
+# FIXME: remove index url once offical sphinx-pyscript 0.2.0
+--extra-index-url https://test.pypi.org/simple/
+sphinx-pyscript-temp == 0.2.0
diff --git a/doc/source/_static/css/custom.css b/doc/source/_static/css/custom.css
new file mode 100644
index 000000000..2ae8ea689
--- /dev/null
+++ b/doc/source/_static/css/custom.css
@@ -0,0 +1,19 @@
+.issue-block {
+ border: 1px solid LightGray;
+ padding-left: .5em;
+ padding-top: .5em;
+ padding-bottom: .5em;
+ margin-bottom: .5em;
+}
+
+.issue-sev-high {
+ background-color: Pink;
+}
+
+.issue-sev-medium {
+ background-color: NavajoWhite;
+}
+
+.issue-sev-low {
+ background-color: LightCyan;
+}
diff --git a/doc/source/conf.py b/doc/source/conf.py
index f2a991c11..5e893c488 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -14,6 +14,7 @@
"sphinx.ext.coverage",
"sphinx.ext.viewcode",
"sphinx_copybutton",
+ "sphinx_pyscript_temp", # FIXME: replace with sphinx_pyscript
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
@@ -63,8 +64,20 @@
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
html_theme = "sphinx_rtd_theme"
-# html_static_path = ['static']
+# These folders are copied to the documentation's HTML output
+html_static_path = ["_static"]
+# These paths are either relative to html_static_path
+# or fully qualified paths (eg. https://...)
+html_css_files = [
+ "css/custom.css",
+ # FIXME: setting priority here overrides the outdated pyscript css
+ ("https://pyscript.net/releases/2024.9.2/core.css", {"priority": 500}),
+]
html_theme_options = {}
+html_js_files = [
+ # FIXME: setting priority here overrides the outdated pyscript js
+ ("https://pyscript.net/releases/2024.9.2/core.js", {"type": "module", "priority": 500}),
+]
# Output file base name for HTML help builder.
htmlhelp_basename = f"{project}doc"
diff --git a/doc/source/index.rst b/doc/source/index.rst
index fbf3e205e..77e627d8b 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -17,6 +17,7 @@ Using and Extending Bandit
blacklists/index
formatters/index
faq
+ playground
Contributing
============
diff --git a/doc/source/playground.py b/doc/source/playground.py
new file mode 100644
index 000000000..1637a65a0
--- /dev/null
+++ b/doc/source/playground.py
@@ -0,0 +1,128 @@
+import io
+import logging
+import tokenize
+
+from bandit.core import config
+from bandit.core import docs_utils
+from bandit.core import manager
+from bandit.core import meta_ast
+from bandit.core import metrics
+from bandit.core import node_visitor
+from bandit.core import test_set
+from pyscript import document
+
+
+# Disable noisy output from Bandit getting rendered to page
+logging.basicConfig(level=logging.ERROR)
+
+ISSUE_BLOCK = """
+
+
+
[{test_id}:{test_name}] {test_text}
+
Severity: {severity}
+
Confidence: {confidence}
+
CWE: {cwe}
+
More info: {url}
+
Location: <stdin>:{line_number}:{col_offset}
+
+
+"""
+
+MESSAGE_BLOCK = """
+
+"""
+
+output_element = document.getElementById("output")
+
+
+def run_analysis(code):
+ issue_metrics = metrics.Metrics()
+ scores = []
+ skipped = []
+ filename = ""
+
+ # Clear previous output
+ output_element.innerHTML = ""
+
+ try:
+ fobj = io.BytesIO(code)
+ issue_metrics.begin(filename)
+ data = fobj.read()
+ lines = data.splitlines()
+ issue_metrics.count_locs(lines)
+ nosec_lines = {}
+
+ try:
+ fobj.seek(0)
+ tokens = tokenize.tokenize(fobj.readline)
+ for toktype, tokval, (lineno, _), _, _ in tokens:
+ if toktype == tokenize.COMMENT:
+ nosec_lines[lineno] = manager._parse_nosec_comment(tokval)
+ except tokenize.TokenError:
+ pass
+
+ visitor = node_visitor.BanditNodeVisitor(
+ filename,
+ fobj,
+ metaast=meta_ast.BanditMetaAst(),
+ testset=test_set.BanditTestSet(
+ config.BanditConfig(),
+ profile={
+ "include": [],
+ "exclude": ["B613"], # FIXME: issue #1182
+ },
+ ),
+ debug=False,
+ nosec_lines=nosec_lines,
+ metrics=issue_metrics,
+ )
+ score = visitor.process(code)
+ scores.append(score)
+ issue_metrics.count_issues([score])
+
+ for index, issue in enumerate(visitor.tester.results):
+ url = docs_utils.get_url(issue.test_id)
+ output_element.innerHTML += ISSUE_BLOCK.format(
+ issue_no=index,
+ issue_class=f"issue-sev-{issue.severity.lower()}",
+ test_name=issue.test,
+ test_id=issue.test_id,
+ test_text=issue.text,
+ severity=issue.severity.capitalize(),
+ confidence=issue.confidence.capitalize(),
+ cwe=str(issue.cwe),
+ cwe_link=issue.cwe.link(),
+ url=url,
+ line_number=issue.lineno,
+ col_offset=issue.col_offset,
+ )
+
+ if not visitor.tester.results:
+ output_element.innerHTML += MESSAGE_BLOCK.format(
+ message="No issues identified."
+ )
+ except SyntaxError:
+ output_element.innerHTML += MESSAGE_BLOCK.format(
+ message="Syntax error parsing code."
+ )
+ except Exception:
+ output_element.innerHTML += MESSAGE_BLOCK.format(
+ message="Exception scanning code."
+ )
+
+ issue_metrics.aggregate()
+
+
+def handle_event(event):
+ run_analysis(event.code.encode())
+
+ # prevent default execution
+ return False
+
+
+editor = document.getElementById("editor")
+editor.handleEvent = handle_event
diff --git a/doc/source/playground.rst b/doc/source/playground.rst
new file mode 100644
index 000000000..1669035d4
--- /dev/null
+++ b/doc/source/playground.rst
@@ -0,0 +1,38 @@
+Playground
+==========
+
+Welcome to the Bandit Playground! This interactive web page allows you to
+experience the power of Bandit, a leading Static Application Security Testing
+(SAST) tool designed to help identify security issues in Python code. Bandit
+scans your code for potential vulnerabilities and provides detailed insights
+to help improve the overall security of your application. Whether you’re a
+security professional or a developer looking to secure your codebase, this
+playground offers a hands-on way to experiment with Bandit’s capabilities
+in real-time. Simply paste your Python code into the editor, run the scan,
+and explore the results instantly!
+
+.. py-config::
+
+ splashscreen:
+ autoclose: true
+ packages:
+ - bandit
+
+.. raw:: html
+
+
+
+.. py-script::
+ :file: playground.py
+
+.. raw:: html
+
+