mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
ci(security): consolidated Semgrep + Psalm taint gate (#7306)
* ci(security): add consolidated Semgrep and Psalm taint gate One workflow runs both taint scanners as separate jobs. Semgrep and Psalm treat htmle()/html_escape()/sanitize_uri() as sanitizers, so findings mean request input reaches a browser sink unescaped. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * stabilize psalm taint analysis * Install dependencies before Psalm analysis * Remove obsolete Psalm taint baseline --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
This commit is contained in:
1 parent
a9748b92b8
commit
9476a306de
6 files changed
+272
No files matched your search
@@ -0,0 +1,86 @@
|
|||||||
|
# +-------------------------------------------------------------------------+
|
||||||
|
# | Copyright (C) 2004-2026 The Cacti Group |
|
||||||
|
# | |
|
||||||
|
# | This program is free software; you can redistribute it and/or |
|
||||||
|
# | modify it under the terms of the GNU General Public License |
|
||||||
|
# | as published by the Free Software Foundation; either version 2 |
|
||||||
|
# | of the License, or (at your option) any later version. |
|
||||||
|
# +-------------------------------------------------------------------------+
|
||||||
|
# | Cacti: The Complete RRDtool-based Graphing Solution |
|
||||||
|
# +-------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
name: Security SAST
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [develop, 1.2.x]
|
||||||
|
pull_request:
|
||||||
|
branches: [develop, 1.2.x]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: security-sast-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
semgrep:
|
||||||
|
name: Semgrep taint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python 3.12
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Install Semgrep
|
||||||
|
run: python3 -m pip install semgrep==1.152.0
|
||||||
|
|
||||||
|
- name: Run Semgrep
|
||||||
|
env:
|
||||||
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -n "${BASE_SHA:-}" ]; then
|
||||||
|
semgrep --config .semgrep.yml --error --baseline-commit "$BASE_SHA" .
|
||||||
|
else
|
||||||
|
semgrep --config .semgrep.yml --error .
|
||||||
|
fi
|
||||||
|
|
||||||
|
psalm:
|
||||||
|
name: Psalm taint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
COMPOSER_ALLOW_SUPERUSER: 1
|
||||||
|
PSALM_VERSION: '6.16.1'
|
||||||
|
PSALM_PHAR_SHA256: '7ace71e4f698466e7e16810679a1895ea623060579aff46fcf9fd0f1c09abe00'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v4
|
||||||
|
|
||||||
|
- name: Setup PHP 8.3
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.3'
|
||||||
|
extensions: intl, mysql, gd, ldap, gmp, xml, curl, json, mbstring
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install Composer dependencies
|
||||||
|
run: composer install --prefer-dist --no-progress --no-interaction
|
||||||
|
|
||||||
|
- name: Download and verify pinned Psalm phar
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
curl -fsSL -o psalm.phar \
|
||||||
|
"https://github.com/vimeo/psalm/releases/download/${PSALM_VERSION}/psalm.phar"
|
||||||
|
echo "${PSALM_PHAR_SHA256} psalm.phar" | sha256sum -c -
|
||||||
|
|
||||||
|
- name: Run Psalm taint analysis
|
||||||
|
run: php psalm.phar --taint-analysis --no-progress
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Cacti-aware Semgrep rules.
|
||||||
|
#
|
||||||
|
# Taint-mode rules that understand Cacti's own escapers, so findings are
|
||||||
|
# limited to request input that reaches a browser sink without passing through
|
||||||
|
# one of them. htmle()/html_escape() run htmlspecialchars(); htmlerv() and
|
||||||
|
# html_escape_request_var() are their request-var wrappers; sanitize_uri()
|
||||||
|
# cleans redirect targets. get_request_var() and get_nfilter_request_var()
|
||||||
|
# both return $_REQUEST values untouched (the filtering variant is
|
||||||
|
# get_filter_request_var()), so they are unsanitized sources.
|
||||||
|
rules:
|
||||||
|
- id: cacti-reflected-xss
|
||||||
|
mode: taint
|
||||||
|
languages: [php]
|
||||||
|
severity: ERROR
|
||||||
|
message: >-
|
||||||
|
Request input reaches echo/print without an HTML escaper. Wrap the value
|
||||||
|
in htmle()/html_escape() (or htmlerv()/html_escape_request_var() for
|
||||||
|
request vars) before output to prevent reflected XSS.
|
||||||
|
metadata:
|
||||||
|
cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)"
|
||||||
|
owasp: "A03:2021 - Injection"
|
||||||
|
category: security
|
||||||
|
confidence: MEDIUM
|
||||||
|
pattern-sources:
|
||||||
|
- pattern: $_GET[...]
|
||||||
|
- pattern: $_POST[...]
|
||||||
|
- pattern: $_REQUEST[...]
|
||||||
|
- pattern: get_request_var(...)
|
||||||
|
- pattern: get_nfilter_request_var(...)
|
||||||
|
pattern-sanitizers:
|
||||||
|
- pattern: htmle(...)
|
||||||
|
- pattern: html_escape(...)
|
||||||
|
- pattern: html_escape_request_var(...)
|
||||||
|
- pattern: htmlerv(...)
|
||||||
|
- pattern: sanitize_uri(...)
|
||||||
|
pattern-sinks:
|
||||||
|
- pattern: echo $SINK;
|
||||||
|
- pattern: print $SINK;
|
||||||
|
|
||||||
|
- id: cacti-header-injection
|
||||||
|
mode: taint
|
||||||
|
languages: [php]
|
||||||
|
severity: ERROR
|
||||||
|
message: >-
|
||||||
|
Request input reaches header() without sanitization. Validate or escape
|
||||||
|
the value (e.g. sanitize_uri() for redirect targets) before setting a
|
||||||
|
response header to prevent header injection and open redirects.
|
||||||
|
metadata:
|
||||||
|
cwe: "CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers (HTTP Response Splitting)"
|
||||||
|
owasp: "A03:2021 - Injection"
|
||||||
|
category: security
|
||||||
|
confidence: MEDIUM
|
||||||
|
# Only raw superglobals are sources here. The get_*_request_var() helpers
|
||||||
|
# feed the standard redirect idiom (header('Location: page.php?id=' .
|
||||||
|
# get_request_var('id'))) where 'id' is separately int-validated via
|
||||||
|
# get_filter_request_var(), so treating them as header sinks is low signal.
|
||||||
|
# A raw superglobal concatenated into header() is the genuine injection.
|
||||||
|
pattern-sources:
|
||||||
|
- pattern: $_GET[...]
|
||||||
|
- pattern: $_POST[...]
|
||||||
|
- pattern: $_REQUEST[...]
|
||||||
|
pattern-sanitizers:
|
||||||
|
- pattern: htmle(...)
|
||||||
|
- pattern: html_escape(...)
|
||||||
|
- pattern: html_escape_request_var(...)
|
||||||
|
- pattern: htmlerv(...)
|
||||||
|
- pattern: sanitize_uri(...)
|
||||||
|
pattern-sinks:
|
||||||
|
- pattern: header(...)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Third-party and generated assets. CLI scripts are not listed here on
|
||||||
|
# purpose: they take filenames from argv, which the web-sink rules in
|
||||||
|
# .semgrep.yml do not match, so no blanket ignore is needed for them.
|
||||||
|
include/vendor/
|
||||||
|
include/fa/
|
||||||
|
node_modules/
|
||||||
|
tests/e2e/node_modules/
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Psalm taint-analysis stubs for Cacti's output escapers.
|
||||||
|
*
|
||||||
|
* These declarations exist only to teach Psalm where tainted data is made
|
||||||
|
* safe. The real implementations live in lib/html.php and lib/functions.php;
|
||||||
|
* the signatures below must stay in sync with them. Without these escape
|
||||||
|
* markers Psalm reports XSS taint on every value that is correctly passed
|
||||||
|
* through htmlspecialchars() by way of these helpers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* html_escape() runs the value through htmlspecialchars() with ENT_QUOTES.
|
||||||
|
*
|
||||||
|
* @psalm-taint-escape html
|
||||||
|
*/
|
||||||
|
function html_escape(mixed $string = '') : string {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* htmle() is a thin alias for html_escape().
|
||||||
|
*
|
||||||
|
* @psalm-taint-escape html
|
||||||
|
*/
|
||||||
|
function htmle(mixed $string) : string {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sanitize_uri() strips the characters used to break out of HTML and URL
|
||||||
|
* contexts (angle brackets, quotes, backticks, parentheses, etc.), so both
|
||||||
|
* the html sink and the quote-bearing text sink are satisfied.
|
||||||
|
*
|
||||||
|
* @psalm-taint-escape html
|
||||||
|
* @psalm-taint-escape has_quotes
|
||||||
|
*/
|
||||||
|
function sanitize_uri(string $uri) : string {}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<files psalm-version="6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac">
|
||||||
|
<file src="include/csrf.php">
|
||||||
|
<TaintedHeader>
|
||||||
|
<code><![CDATA['Location: ' . sanitize_uri($_SERVER['REQUEST_URI'])]]></code>
|
||||||
|
</TaintedHeader>
|
||||||
|
</file>
|
||||||
|
<file src="include/global.php">
|
||||||
|
<TaintedHeader>
|
||||||
|
<code><![CDATA["Content-Security-Policy: default-src *; img-src 'self' https://api.qrserver.com $alternates data: blob:; style-src 'self' 'unsafe-inline' $alternates; script-src 'self' $script_policy 'unsafe-inline' $alternates; frame-ancestors 'self'; worker-src 'self' $alternates;"]]></code>
|
||||||
|
</TaintedHeader>
|
||||||
|
</file>
|
||||||
|
<file src="include/global_session.php">
|
||||||
|
<TaintedHtml>
|
||||||
|
<code><![CDATA[gfrv('action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([-a-zA-Z0-9_\s]+)$/']])]]></code>
|
||||||
|
</TaintedHtml>
|
||||||
|
<TaintedTextWithQuotes>
|
||||||
|
<code><![CDATA[gfrv('action', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^([-a-zA-Z0-9_\s]+)$/']])]]></code>
|
||||||
|
</TaintedTextWithQuotes>
|
||||||
|
</file>
|
||||||
|
<file src="install/functions.php">
|
||||||
|
<TaintedFile>
|
||||||
|
<code><![CDATA[$cacheFile]]></code>
|
||||||
|
</TaintedFile>
|
||||||
|
<TaintedHtml>
|
||||||
|
<code><![CDATA[$data]]></code>
|
||||||
|
</TaintedHtml>
|
||||||
|
</file>
|
||||||
|
<file src="lib/auth.php">
|
||||||
|
<TaintedHeader>
|
||||||
|
<code><![CDATA['Location: ' . $referer]]></code>
|
||||||
|
</TaintedHeader>
|
||||||
|
</file>
|
||||||
|
<file src="lib/functions.php">
|
||||||
|
<TaintedHtml>
|
||||||
|
<code><![CDATA[$omessage]]></code>
|
||||||
|
<code><![CDATA['Message: ' . trim($string) . PHP_EOL]]></code>
|
||||||
|
</TaintedHtml>
|
||||||
|
<TaintedTextWithQuotes>
|
||||||
|
<code><![CDATA[$omessage]]></code>
|
||||||
|
<code><![CDATA['Message: ' . trim($string) . PHP_EOL]]></code>
|
||||||
|
</TaintedTextWithQuotes>
|
||||||
|
</file>
|
||||||
|
</files>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<psalm
|
||||||
|
errorLevel="8"
|
||||||
|
resolveFromConfigFile="true"
|
||||||
|
autoloader="include/vendor/autoload.php"
|
||||||
|
errorBaseline="psalm-baseline.xml"
|
||||||
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
|
>
|
||||||
|
<projectFiles>
|
||||||
|
<directory name="lib" />
|
||||||
|
<directory name="install" />
|
||||||
|
<directory name="cli" />
|
||||||
|
<directory name="include" />
|
||||||
|
<file name="managers.php" />
|
||||||
|
<file name="package_import.php" />
|
||||||
|
<file name="graphs.php" />
|
||||||
|
<file name="host.php" />
|
||||||
|
<file name="reports.php" />
|
||||||
|
<file name="graph_view.php" />
|
||||||
|
<file name="data_sources.php" />
|
||||||
|
<file name="aggregate_graphs.php" />
|
||||||
|
<ignoreFiles>
|
||||||
|
<directory name="include/vendor" />
|
||||||
|
<directory name="include/fa" />
|
||||||
|
</ignoreFiles>
|
||||||
|
</projectFiles>
|
||||||
|
|
||||||
|
<stubs>
|
||||||
|
<file name="build/psalm/cacti-sanitizers.phpstub" />
|
||||||
|
</stubs>
|
||||||
|
</psalm>
|
||||||
Reference in new issue
Block a user