Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/secuscan/parser_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def _read_stderr() -> None:
plugin_id,
_sanitize_stderr(stderr_text),
)
raise ParserSandboxError(plugin_id, f"timed out after {timeout_seconds}s")
raise ParserSandboxError(plugin_id, f"timed out after {timeout_seconds}s", stderr_text)

if proc.returncode != 0:
logger.error(
Expand All @@ -280,6 +280,7 @@ def _read_stderr() -> None:
raise ParserSandboxError(
plugin_id,
f"subprocess exited with code {proc.returncode}",
stderr_text,
)

stdout_bytes = b"".join(stdout_chunks)
Expand All @@ -302,6 +303,7 @@ def _read_stderr() -> None:
raise ParserSandboxError(
plugin_id,
f"parser returned non-JSON output: {exc}",
stderr_text,
)

if not isinstance(parsed, (dict, list)):
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Background.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface BackgroundProps {

export default function Background({ state = 'idle' }: BackgroundProps) {
return (
<div className={`background background--${state}`}>
<div className={`background background--${state}`} aria-hidden="true">
<div className="background-grid" />
<div className="background-scan" />
<div className="background-lines" />
Expand Down
34 changes: 34 additions & 0 deletions frontend/testing/unit/components/Background.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import { render } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import Background from '../../../src/components/Background'

describe('Background Component', () => {
it('renders without crashing with default props', () => {
const { container } = render(<Background />)
const wrapper = container.querySelector('.background')
expect(wrapper).toBeInTheDocument()
expect(wrapper).toHaveClass('background--idle')
})

it('renders with different state props', () => {
const { container: activeContainer } = render(<Background state="active" />)
expect(activeContainer.querySelector('.background')).toHaveClass('background--active')

const { container: errorContainer } = render(<Background state="error" />)
expect(errorContainer.querySelector('.background')).toHaveClass('background--error')
})

it('asserts decorative container and layers are aria-hidden', () => {
const { container } = render(<Background />)

// The main wrapper should be aria-hidden since it is decorative background
const wrapper = container.querySelector('.background')
expect(wrapper).toHaveAttribute('aria-hidden', 'true')

// Confirm that the decorative internal elements exist as well
expect(container.querySelector('.background-grid')).toBeInTheDocument()
expect(container.querySelector('.background-scan')).toBeInTheDocument()
expect(container.querySelector('.background-lines')).toBeInTheDocument()
})
})
6 changes: 3 additions & 3 deletions testing/backend/unit/test_parser_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def parse(output):
)
with pytest.raises(ParserSandboxError) as exc_info:
run_parser_in_sandbox(p, "verbose_crash", "data")
assert "detailed crash info" in exc_info.value.stderr_excerpt
assert "detailed crash info" in exc_info.value._stderr_diagnostic

def test_syntax_error_in_parser_raises(self, tmp_path):
p = tmp_path / "parser.py"
Expand Down Expand Up @@ -396,9 +396,9 @@ def test_reason_stored(self):
err = ParserSandboxError("plugin_x", "custom reason")
assert err.reason == "custom reason"

def test_stderr_excerpt_truncated_to_2000_chars(self):
def test_stderr_diagnostic_stored(self):
err = ParserSandboxError("p", "r", stderr="x" * 5000)
assert len(err.stderr_excerpt) == 2000
assert err._stderr_diagnostic == "x" * 5000

def test_str_contains_plugin_id(self):
err = ParserSandboxError("my_plugin", "bad thing")
Expand Down
7 changes: 3 additions & 4 deletions testing/backend/unit/test_parser_sandbox_timeout_cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ def test_timeout_expired_raises_parser_sandbox_error(self, tmp_path):

with pytest.raises(subprocess.TimeoutExpired):
proc = subprocess.Popen(
["python3", "-c", _BOOTSTRAP_TEMPLATE.format(
parser_path=str(parser),
["python3", "-c", _BOOTSTRAP_TEMPLATE.safe_substitute(
parser_path_repr=repr(str(parser)),
max_input_bytes=1024,
)],
stdin=subprocess.PIPE,
Expand Down Expand Up @@ -93,8 +93,7 @@ def test_timeout_error_includes_stderr(self, tmp_path):
with pytest.raises(Exception) as exc_info:
run_parser_in_sandbox(parser, "stderr_plugin", "data", timeout_seconds=1)

exc_message = str(exc_info.value)
assert "early error" in exc_message
assert "early error" in exc_info.value._stderr_diagnostic

def test_multiple_consecutive_timeouts_do_not_leak_resources(self, tmp_path):
"""Running multiple timeouts in sequence must not accumulate open file handles."""
Expand Down
2 changes: 1 addition & 1 deletion testing/backend/unit/test_plugin_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ def test_sandbox_exec_fails_on_exec_statement(self):
plugin_id="forbidden_parser_plugin",
parser_input="test input"
)
assert "ValueError" in exc_info.value.stderr_excerpt or "exec" in exc_info.value.stderr_excerpt or "sandbox exec test" in exc_info.value.stderr_excerpt
assert "ValueError" in exc_info.value._stderr_diagnostic or "exec" in exc_info.value._stderr_diagnostic or "sandbox exec test" in exc_info.value._stderr_diagnostic

def test_sandbox_strips_environ_secrets(self, monkeypatch):
"""Parser sandbox environment variables should be stripped to avoid secret leakage."""
Expand Down
Loading