7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
149 lines
4.4 KiB
Python
Executable File
149 lines
4.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Pre-commit hook to detect double-escaping in JavaScript files.
|
|
|
|
Catches patterns where escapeHtml() is called on values that are then
|
|
passed to functions which already escape internally (showError, showSuccess,
|
|
showInfo, showAlert).
|
|
|
|
Known limitations:
|
|
- Multi-line function calls are not detected (line-by-line processing).
|
|
- Variable indirection bypasses detection
|
|
(e.g., ``const safe = escapeHtml(msg); showError(safe);``).
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
|
|
|
|
# Functions that escape their arguments internally
|
|
ESCAPING_FUNCTIONS = ["showError", "showSuccess", "showInfo", "showAlert"]
|
|
|
|
# Build pattern: showError(...escapeHtml(...)...)
|
|
PATTERN = re.compile(
|
|
r"\b(" + "|".join(ESCAPING_FUNCTIONS) + r")\s*\("
|
|
r".*?"
|
|
r"(?:escapeHtml|XSSProtection\.escapeHtml)\s*\("
|
|
)
|
|
|
|
|
|
def strip_js_comments(line, in_block_comment):
|
|
"""Strip JavaScript comments from a line, respecting string literals.
|
|
|
|
Handles ``//`` line comments, ``/* */`` block comments (including
|
|
multi-line), and correctly ignores comment-like sequences inside
|
|
single-quoted, double-quoted, and template-literal strings.
|
|
|
|
Returns ``(code_without_comments, still_in_block_comment)``.
|
|
"""
|
|
result = []
|
|
i = 0
|
|
in_single_quote = False
|
|
in_double_quote = False
|
|
in_template = False
|
|
|
|
while i < len(line):
|
|
ch = line[i]
|
|
|
|
# Inside a block comment — scan for */
|
|
if in_block_comment:
|
|
if ch == "*" and i + 1 < len(line) and line[i + 1] == "/":
|
|
in_block_comment = False
|
|
i += 2
|
|
else:
|
|
i += 1
|
|
continue
|
|
|
|
# Handle escape sequences inside strings
|
|
if (in_single_quote or in_double_quote or in_template) and ch == "\\":
|
|
result.append(ch)
|
|
if i + 1 < len(line):
|
|
result.append(line[i + 1])
|
|
i += 2
|
|
else:
|
|
i += 1
|
|
continue
|
|
|
|
# Track string state
|
|
if ch == "'" and not in_double_quote and not in_template:
|
|
in_single_quote = not in_single_quote
|
|
elif ch == '"' and not in_single_quote and not in_template:
|
|
in_double_quote = not in_double_quote
|
|
elif ch == "`" and not in_single_quote and not in_double_quote:
|
|
in_template = not in_template
|
|
elif not in_single_quote and not in_double_quote and not in_template:
|
|
# Outside strings — check for comments
|
|
if ch == "/" and i + 1 < len(line):
|
|
if line[i + 1] == "/":
|
|
break # Line comment — rest of line is comment
|
|
if line[i + 1] == "*":
|
|
in_block_comment = True
|
|
i += 2
|
|
continue
|
|
|
|
result.append(ch)
|
|
i += 1
|
|
|
|
return "".join(result), in_block_comment
|
|
|
|
|
|
def check_file(file_path):
|
|
"""Check a JavaScript file for double-escaping patterns."""
|
|
issues = []
|
|
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
except (UnicodeDecodeError, IOError):
|
|
# Skip binary files or files we can't read
|
|
return []
|
|
|
|
in_block_comment = False
|
|
for line_num, line in enumerate(lines, 1):
|
|
code, in_block_comment = strip_js_comments(line, in_block_comment)
|
|
|
|
if PATTERN.search(code):
|
|
issues.append(
|
|
f"{file_path}:{line_num}: Possible double-escaping — "
|
|
f"escapeHtml() inside a function that already escapes: "
|
|
f"{line.strip()}"
|
|
)
|
|
|
|
return issues
|
|
|
|
|
|
def main():
|
|
"""Main function to check all provided files."""
|
|
if len(sys.argv) < 2:
|
|
print("No files to check")
|
|
return 0
|
|
|
|
all_issues = []
|
|
|
|
for file_path in sys.argv[1:]:
|
|
if not file_path.endswith(".js"):
|
|
continue
|
|
|
|
try:
|
|
issues = check_file(file_path)
|
|
all_issues.extend(issues)
|
|
except Exception as e:
|
|
print(f"Error checking {file_path}: {e}")
|
|
continue
|
|
|
|
if all_issues:
|
|
print("Double-escaping detected:")
|
|
print("-" * 60)
|
|
for issue in all_issues:
|
|
print(f" {issue}")
|
|
print("-" * 60)
|
|
print("\nThese functions already escape their arguments internally.")
|
|
print("Pass raw text instead of pre-escaping with escapeHtml().")
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|