Skip to content

Commit

Permalink
Add a new playground to the docs
Browse files Browse the repository at this point in the history
This change introduces a new playground in the docs where a
user can test out Bandit right within their browser. This
code uses PyScript (and sphinx-pyscript) to generate an editor
window on a sphinx page in our docs. When the user clicks the
play button, it runs Bandit against the example code they have
provided.

If Bandit finds issues it renders them in a box on the same page
under the editable code.

The editor windows by default includes an example of correct and
incorrect usage of ssl context.

Signed-off-by: Eric Brown <[email protected]>
  • Loading branch information
ericwb committed Oct 4, 2024
1 parent bcb6648 commit 381fea8
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 1 deletion.
4 changes: 4 additions & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 19 additions & 0 deletions doc/source/_static/css/custom.css
Original file line number Diff line number Diff line change
@@ -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;
}
15 changes: 14 additions & 1 deletion doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Using and Extending Bandit
blacklists/index
formatters/index
faq
playground

Contributing
============
Expand Down
130 changes: 130 additions & 0 deletions doc/source/playground.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import io
import logging
import sys
import tokenize
import traceback

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 = """
<div id="issue-{issue_no}">
<div class="issue-block {issue_class}">
<b>[{test_id}:{test_name}]</b> {test_text}<br>
<b>Severity: </b>{severity}<br>
<b>Confidence: </b>{confidence}<br>
<b>CWE: </b><a href="{cwe_link}" target="_blank">{cwe}</a><br>
<b>More info: </b><a href="{url}" target="_blank">{url}</a><br>
<b>Location: </b>&lt;stdin&gt;:{line_number}:{col_offset}<br>
</div>
</div>
"""

MESSAGE_BLOCK = """
<div id="no-issues">
<div class="issue-block">
<b>{message}</b><br>
</div>
</div>
"""

output_element = document.getElementById("output")


def run_analysis(code):
issue_metrics = metrics.Metrics()
scores = []
skipped = []
filename = "<stdin>"

# 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
38 changes: 38 additions & 0 deletions doc/source/playground.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Bandit 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

<script type="py-editor" id="editor">
import ssl
# Correct
context = ssl.create_default_context()
# Incorrect: unverified context
context = ssl._create_unverified_context()
</script>

.. py-script::
:file: playground.py

.. raw:: html

<div id="output"></div>

0 comments on commit 381fea8

Please sign in to comment.