diff --git a/.websec-ignore b/.websec-ignore index 0d95646..058e810 100644 --- a/.websec-ignore +++ b/.websec-ignore @@ -48,3 +48,14 @@ src/websec_validator/briefing.py # The AppSync GraphQL "surface" is detected purely from our own `@aws_*` example strings — no real # GraphQL server lives in this repo, so the synthetic graphql finding is a self-reference. category:graphql +# Extractor code defining attack patterns, ignored to prevent self-matching noise +src/websec_validator/extractors/llm_security.py +# Extractor code defining attack patterns, ignored to prevent self-matching noise +src/websec_validator/extractors/authz.py +# Extractor code defining attack patterns, ignored to prevent self-matching noise +src/websec_validator/extractors/crypto_usage.py +# Extractor code defining attack patterns, ignored to prevent self-matching noise +src/websec_validator/extractors/transport_security.py + +# findings.py contains the text of the reports and findings definitions (e.g. 'fail-open' strings). +src/websec_validator/findings.py diff --git a/Dockerfile b/Dockerfile index bc826c2..36a35a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,5 +51,8 @@ RUN pip install --no-cache-dir . RUN useradd --create-home --uid 1001 websec WORKDIR /scan USER websec + +HEALTHCHECK --interval=30s --timeout=3s CMD websec --help || exit 1 + ENTRYPOINT ["websec"] CMD ["--help"] diff --git a/src/websec_validator/extractors/llm_security.py b/src/websec_validator/extractors/llm_security.py index b0815d2..4ea98c6 100644 --- a/src/websec_validator/extractors/llm_security.py +++ b/src/websec_validator/extractors/llm_security.py @@ -67,7 +67,7 @@ HUMAN_GATE = re.compile(r"confirm\w*|requireApproval|humanInTheLoop|awaitConfirmation|pendingApproval|review\w*Required", re.I) # guardrail / moderation that FAILS OPEN -GUARD_FN = re.compile(r"\b(?:guard|scan(?:Input|Output)?|moderat\w*|checkContent|safetyCheck|nemo|llmGuard)\w*\s*[\(=]", re.I) +GUARD_FN = re.compile(r"\b(?:guard|scan(?:Input|Output)?|moderat\w*|checkContent|safetyCheck|nemo|llmGuard)\s*[\(=]", re.I) FAIL_OPEN = re.compile( r"catch[^{]*\{[^}]*\breturn\b[^}]*(?:allowed\s*:\s*true|action\s*:\s*['\"](?:allow|error|continue)|true)" r"|fail[-_ ]?open|continuing[^.\n]{0,20}(?:fail|open)|return\s*\{\s*allowed\s*:\s*true", re.I) diff --git a/tests/test_recon.py b/tests/test_recon.py index a22c976..9c26780 100644 --- a/tests/test_recon.py +++ b/tests/test_recon.py @@ -1110,6 +1110,11 @@ def test_guardrail_fail_open_flagged(self): "catch (e) { return { allowed: true }; } }"}) self.assertIn("llm-guardrail-fail-open", self._kinds(out)) + def test_guardrail_fail_open_false_positives(self): + out = self._llm({"guard.ts": "const Scanner = () => {}; const scan_dir = '/tmp';" + "catch (e) { return { allowed: true }; }"}) + self.assertNotIn("llm-guardrail-fail-open", self._kinds(out)) + # --- docker-compose host exposure --- def _compose(self, body): d = Path(tempfile.mkdtemp())