npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

qastell

v0.9.2

Published

Security audit library for Playwright, Puppeteer, Cypress, and Selenium WebDriver test automation

Readme

QAstell

QA + Castell - Security Auditing for Playwright, Puppeteer, Cypress & Selenium WebDriver

npm version License

A security audit library that integrates directly into your test automation workflow. Run security checks alongside your functional tests and catch common vulnerabilities early.

The Name: Defense is universal. Castle in English, Kastell in German, Castell in Welsh, Castel in Romanian – all from Latin castellum, the fortress that protects what matters. QAstell brings that same principle to test automation: a fortress built into your CI/CD pipeline.

Why QAstell?

Security Shift-Left

Traditional security testing happens late in the development cycle - often just before release or during dedicated security audits. By this point, vulnerabilities are expensive to fix and may delay releases.

QAstell enables security shift-left by integrating security checks directly into your existing tests:

  • SDETs and QA engineers can identify potential security issues during regular test runs
  • Developers get immediate feedback when they introduce security regressions
  • Security teams can focus on complex, application-specific vulnerabilities instead of chasing common misconfigurations

Complementary, Not Replacement

Important: QAstell is designed to complement, not replace, your existing security tools and practices.

QAstell does not replace SAST tools (SonarQube, Checkmarx), DAST tools (OWASP ZAP, Burp Suite), penetration testing, or security code reviews.

Instead, QAstell fills a gap: continuous, automated detection of common client-side security issues during functional testing. Think of it as an additional safety net that catches low-hanging fruit early, freeing your security specialists to focus on the harder problems.

Who Should Use QAstell?

  • SDETs who want to add security value to their test suites
  • QA teams looking to catch security regressions before they reach staging
  • Development teams practicing DevSecOps
  • Small teams without dedicated security resources who want basic coverage
  • Anyone who believes security is everyone's responsibility

Features

  • Multi-Framework Support - Works with Playwright, Puppeteer, Cypress, and Selenium WebDriver
  • 250+ Security Rules - Covers common web security issues across 48 categories
  • CVSS Severity Model - Critical/High/Medium/Low/Info with OWASP & CWE references
  • Multiple Report Formats - JSON, interactive HTML, and SARIF (GitHub/GitLab compatible)
  • Interactive HTML Reports - Filter by severity, category, hide/show rules
  • Configurable - Include/exclude rules, set thresholds, skip specific checks

Try It Now - 30 Seconds

No setup needed. Copy, paste, run.

Playwright (one command)

npx -y create-playwright@latest qastell-demo --quiet && cd qastell-demo && npm i qastell && echo 'import{test}from"@playwright/test";import{SecurityAuditor}from"qastell";test("security",async({page})=>{await page.goto("https://example.com");const a=new SecurityAuditor(page);const r=await a.audit();console.log("Issues:",r.summary.total,"| Critical:",r.summary.bySeverity.critical,"| High:",r.summary.bySeverity.high);});' > tests/security.spec.ts && npx playwright test security --reporter=list

Puppeteer (one command)

mkdir -p qastell-demo && cd qastell-demo && npm init -y && npm i qastell puppeteer && node -e 'const p=require("puppeteer"),{SecurityAuditor}=require("qastell");(async()=>{const b=await p.launch(),pg=await b.newPage();await pg.goto("https://example.com");const a=new SecurityAuditor(pg),r=await a.audit();console.log("Issues:",r.summary.total,"| Critical:",r.summary.bySeverity.critical,"| High:",r.summary.bySeverity.high);await b.close()})();'

Selenium WebDriver (one command)

mkdir -p qastell-demo && cd qastell-demo && npm init -y && npm i qastell selenium-webdriver && node -e 'const{Builder}=require("selenium-webdriver"),chrome=require("selenium-webdriver/chrome"),{SecurityAuditor}=require("qastell");(async()=>{const o=new chrome.Options();o.addArguments("--headless","--no-sandbox");const d=await new Builder().forBrowser("chrome").setChromeOptions(o).build();await d.get("https://example.com");const a=new SecurityAuditor(d),r=await a.audit();console.log("Issues:",r.summary.total,"| Critical:",r.summary.bySeverity.critical,"| High:",r.summary.bySeverity.high);await d.quit()})();'

Cypress (one command)

mkdir -p qastell-demo/cypress/e2e && cd qastell-demo && npm init -y && npm i qastell cypress && echo 'const{defineConfig}=require("cypress");module.exports=defineConfig({e2e:{supportFile:false}})' > cypress.config.js && echo 'const{SecurityAuditor}=require("qastell");it("security",()=>{cy.visit("https://example.com");cy.window().then(async(win)=>{const a=new SecurityAuditor(win),r=await a.audit();cy.log("Issues: "+r.summary.total+" | Critical: "+r.summary.bySeverity.critical+" | High: "+r.summary.bySeverity.high)})})' > cypress/e2e/security.cy.js && npx cypress run --spec cypress/e2e/security.cy.js

Note: This one-liner uses JavaScript for simplicity. See the examples for Cypress + TypeScript setups.

Note: First-time Playwright users may need to run npx playwright install to install browser binaries.


Installation

npm install qastell

Quick Start

Playwright

import { test } from '@playwright/test';
import { SecurityAuditor } from 'qastell';

test('security audit', async ({ page }) => {
  await page.goto('https://example.com');

  const auditor = new SecurityAuditor(page);
  await auditor.assertNoViolations();
});

Puppeteer

const puppeteer = require('puppeteer');
const { SecurityAuditor } = require('qastell');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');

  const auditor = new SecurityAuditor(page);
  const results = await auditor.audit();

  console.log(`Found ${results.summary.total} violations`);
  await browser.close();
})();

Selenium WebDriver

const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const { SecurityAuditor } = require('qastell');

(async () => {
  const options = new chrome.Options();
  options.addArguments('--headless', '--no-sandbox');

  const driver = await new Builder()
    .forBrowser('chrome')
    .setChromeOptions(options)
    .build();
  await driver.get('https://example.com');

  const auditor = new SecurityAuditor(driver);
  const results = await auditor.audit();

  console.log(`Found ${results.summary.total} violations`);
  await driver.quit();
})();

Cypress

import { SecurityAuditor } from 'qastell';

describe('Security', () => {
  it('should pass security audit', () => {
    cy.visit('https://example.com');

    cy.window().then(async (win) => {
      const auditor = new SecurityAuditor(win);
      const results = await auditor.audit();

      cy.log(`Found ${results.summary.total} violations`);
      await auditor.assertNoViolations();
    });
  });
});

Framework Compatibility

| Feature | Playwright | Puppeteer | Cypress | WebDriver | |---------|:----------:|:---------:|:-------:|:---------:| | DOM Analysis | ✅ | ✅ | ✅ | ✅ | | Cookie Inspection | ✅ | ✅ | ⚠️* | ✅ | | JavaScript Evaluation | ✅ | ✅ | ✅ | ✅ | | HTTP Response Headers | ✅ | ✅ | ❌ | ❌ | | CSP Meta Tag Detection | ✅ | ✅ | ✅ | ✅ | | Storage Analysis | ✅ | ✅ | ✅ | ✅ | | Shadow DOM Inspection | ✅ | ✅ | ✅ | ✅ | | All 250+ Rules | ✅ | ✅ | ~245** | ~245** |

* Cypress cookie inspection only sees non-httpOnly cookies via the adapter. Use cy.getCookies() for full cookie info.

** Cypress and WebDriver cannot access HTTP response headers, so ~5 header-related rules are skipped automatically.

Usage Examples

Basic Audit

const auditor = new SecurityAuditor(page);
const results = await auditor.audit();

console.log(`Found ${results.summary.total} violations`);
console.log(`By severity:`, results.summary.bySeverity);

With Configuration

const results = await auditor.audit({
  include: ['links', 'forms'],      // Only check these categories
  exclude: ['sensitive-autocomplete'], // Skip this category
  skipRules: ['missing-noopener'],  // Skip specific rules
  thresholds: {
    critical: 0,  // Fail if any critical issues
    high: 2,      // Allow up to 2 high severity issues
  }
});

Assertion with Allowed Violations

await auditor.assertNoViolations({
  include: ['links', 'forms'],
  allowedViolations: ['missing-noopener'], // Ignore this specific rule
});

Generate Reports

const results = await auditor.audit();

// Full interactive HTML report (all tiers)
const html = results.toHTML();
fs.writeFileSync('report.html', html);

// Compact summary HTML (smaller, faster - great for attachments)
const summary = results.toSummaryHTML();
fs.writeFileSync('summary.html', summary);

// JSON report (Enterprise+)
const json = results.toJSON();
fs.writeFileSync('report.json', json);

// SARIF report (Corporate only - GitHub/GitLab compatible)
const sarif = results.toSARIF();
fs.writeFileSync('report.sarif', sarif);

Playwright Test Attachments

Attach the HTML report directly to Playwright's test reporter - the report appears as a clickable link in Playwright's HTML report:

import { test } from '@playwright/test';
import { SecurityAuditor } from 'qastell';

test('security audit', async ({ page }, testInfo) => {
  await page.goto('https://example.com');

  const auditor = new SecurityAuditor(page);
  const results = await auditor.audit();

  // Attach report to Playwright's HTML reporter
  await results.attachReport(testInfo);

  // Or with a custom name
  await results.attachReport(testInfo, 'homepage-security');
});

Run with npx playwright test --reporter=html and click "security-report" in the test details to view the full QAstell report.

Playwright Fixture (Auto-Attach)

For automatic report attachment, use the QAstell Playwright fixture. Reports are automatically attached to every test that runs a security audit:

// Use QAstell's test fixture instead of Playwright's
import { test, expect } from 'qastell/fixtures/playwright';

test('security audit', async ({ page, securityAuditor }) => {
  await page.goto('https://example.com');

  const results = await securityAuditor.audit();
  expect(results.passed()).toBe(true);

  // Report is automatically attached - no need to call attachReport()!
});

The fixture:

  • Provides a pre-configured securityAuditor instance tied to your page
  • Automatically attaches the HTML report after each test that calls audit()
  • Only attaches when an audit was run (tests without audits won't have attachments)

Security Rules (250+ rules across 48 categories)

QAstell checks for common security issues including:

| Category | Example Rules | |----------|--------------| | Links | Missing rel="noopener", JavaScript URLs | | Forms | Missing CSRF tokens, insecure form actions | | Headers | Missing CSP, X-Frame-Options, HSTS | | Cookies | Missing Secure/HttpOnly/SameSite flags | | XSS Vectors | Inline handlers, innerHTML sinks, eval() | | DOM Security | DOM clobbering, prototype pollution | | Third-Party | Ad network scripts without SRI | | Storage | Sensitive data in localStorage | | CORS | Wildcard origins, credential leaks | | And more... | Shadow DOM, WebSockets, Service Workers, etc. |

Each rule includes OWASP Top 10 and CWE references, plus remediation guidance.

| Rule ID | Category | Severity | Description | |---------|----------|----------|-------------| | missing-noopener | links | medium | Links with target="_blank" missing rel="noopener" | | javascript-url | links | high | Links using javascript: URLs (XSS vector) | | missing-csrf-token | forms | high | POST forms without CSRF token protection | | sensitive-autocomplete | forms | low | Password/credit card fields allowing autocomplete | | insecure-form-action | forms | high | HTTPS page with HTTP form action | | inline-event-handlers | inline-handlers | medium | Inline event handlers (onclick, onload, etc.) | | missing-iframe-sandbox | iframes | medium | Cross-origin iframes missing sandbox attribute | | dangerous-iframe-sandbox | iframes | high | Iframes with allow-scripts + allow-same-origin | | mixed-content | mixed-content | high | HTTP resources loaded on HTTPS pages | | missing-csp-meta | headers | info | No Content Security Policy meta tag | | base-tag-detected | headers | info | Base tag present (potential hijacking vector) | | no-frame-protection-meta | headers | info | No clickjacking protection in meta tags | | missing-sri | sri | medium | External scripts/stylesheets without integrity attribute | | weak-sri-hash | sri | low | SRI using only SHA-256 instead of SHA-384/512 | | exposed-email | sensitive-data | low | Email addresses exposed in page source | | exposed-api-key | sensitive-data | high | Potential API keys/tokens in page source | | comments-with-secrets | sensitive-data | low | HTML comments with sensitive content | | shadow-dom-inline-handlers | shadow-dom | high | Inline handlers in Shadow DOM (CSP bypass) | | open-shadow-dom | shadow-dom | info | Open Shadow DOM with forms/sensitive inputs | | shadow-dom-form-no-csrf | shadow-dom | high | Shadow DOM forms without CSRF protection | | dom-clobbering-globals | dom-clobbering | high | Element IDs shadowing global properties | | dom-clobbering-collections | dom-clobbering | medium | Multiple elements creating exploitable collections | | form-property-clobbering | dom-clobbering | high | Form inputs clobbering form properties | | ad-network-no-sri | third-party | high | Ad network scripts without SRI | | ad-iframe-no-sandbox | third-party | medium | Ad iframes without sandbox | | third-party-document-write | third-party | medium | Third-party scripts using document.write | | excessive-third-party | third-party | info | Too many third-party script domains | | tracking-on-sensitive-page | third-party | medium | Tracking scripts on login/payment pages | | script-wrong-extension | mime-type | medium | Scripts with non-JS extensions | | stylesheet-wrong-extension | mime-type | low | Stylesheets with non-CSS extensions | | script-type-misuse | mime-type | low | Non-executing script types with code | | data-uri-script | mime-type | high | Scripts loaded from data: URIs | | url-prototype-pollution | prototype-pollution | high | URL contains proto/constructor patterns | | unsafe-object-merge | prototype-pollution | medium | Scripts merging external data unsafely | | polluted-prototype | prototype-pollution | critical | Object.prototype has been polluted | | modifies-prototype | prototype-pollution | medium | Script modifies built-in prototypes | | link-redirect-parameter | open-redirect | medium | Links with redirect URL parameters | | form-redirect-parameter | open-redirect | medium | Forms with redirect parameters | | meta-refresh-redirect | open-redirect | medium | Meta refresh to external URL | | js-location-redirect | open-redirect | high | JavaScript redirect using URL input | | opener-accessible | open-redirect | low | window.opener accessible (tabnabbing) | | postmessage-no-origin-check | postmessage | high | postMessage listener without origin validation | | postmessage-wildcard-origin | postmessage | medium | postMessage uses wildcard (*) target origin | | postmessage-dangerous-sink | postmessage | high | postMessage handler uses dangerous sinks | | localstorage-sensitive-data | storage | high | Sensitive data detected in localStorage | | sessionstorage-sensitive-data | storage | medium | Sensitive data detected in sessionStorage | | storage-unencrypted-data | storage | info | Large unencrypted data in web storage | | storage-no-error-handling | storage | low | Storage access without error handling | | dangling-markup-attribute | dangling-markup | high | Potentially dangling markup detected | | base-tag-hijacking | dangling-markup | critical | Base tag points to external origin | | form-external-action | dangling-markup | medium | Form submits to external domain | | meta-refresh-exfiltration | dangling-markup | high | Meta refresh with potential data exfiltration | | noscript-dangling-markup | dangling-markup | low | Noscript tag with suspicious content | | css-external-import | css-injection | medium | CSS @import from external source | | css-external-url | css-injection | low | CSS url() references external domain | | css-attribute-selector | css-injection | medium | CSS attribute selector could leak data | | css-expression | css-injection | high | CSS expression or behavior detected | | idn-homograph | unicode | high | IDN homograph attack in link | | rtl-override | unicode | high | Right-to-left override character detected | | null-byte-url | unicode | high | Null byte or dangerous character in URL | | invisible-characters | unicode | low | Invisible/zero-width characters detected | | innerhtml-sink | html-injection | high | innerHTML used with external data | | document-write-sink | html-injection | medium | document.write usage detected | | eval-usage | html-injection | high | eval() or similar function detected | | user-controlled-selector | html-injection | medium | querySelector with external input | | template-literal-injection | html-injection | medium | Template literal with external data | | service-worker-cross-origin | service-worker | critical | Service worker from different origin | | service-worker-broad-scope | service-worker | info | Service worker with root scope | | service-worker-no-https | service-worker | medium | Service worker on non-HTTPS page | | service-worker-suspicious-patterns | service-worker | medium | Suspicious service worker patterns | | fullscreen-abuse | clickjacking | medium | Fullscreen API usage detected | | pointer-events-manipulation | clickjacking | low | Pointer-events manipulation detected | | opacity-hiding | clickjacking | medium | Low opacity element covering content | | user-select-manipulation | clickjacking | low | Text selection disabled | | cookie-no-httponly | cookies | high | Sensitive cookie accessible to JS | | cookie-no-secure | cookies | medium | Cookie without Secure flag on HTTPS | | cookie-no-samesite | cookies | medium | Cookie without SameSite attribute | | cookie-broad-scope | cookies | low | Cookie with overly broad scope | | cookie-sensitive-value | cookies | medium | Cookie may contain sensitive data | | websocket-insecure | websocket | high | Unencrypted WebSocket on HTTPS | | websocket-cross-origin | websocket | medium | WebSocket to different origin | | websocket-no-auth | websocket | medium | WebSocket without authentication | | websocket-message-sink | websocket | high | WebSocket message in dangerous sink | | svg-script-content | mutation-xss | high | SVG contains script or handlers | | mathml-namespace-abuse | mutation-xss | medium | MathML namespace confusion | | nested-parsing-abuse | mutation-xss | medium | Dangerous element nesting pattern | | style-tag-abuse | mutation-xss | low | Style tag in unusual context | | unsafe-dom-parsing | mutation-xss | high | DOMParser without sanitization | | private-ip-access | dns-rebinding | high | Request to private IP range detected | | host-header-unvalidated | dns-rebinding | medium | Host header validation missing | | websocket-rebinding-risk | dns-rebinding | medium | WebSocket rebinding vulnerability | | cors-rebinding-risk | dns-rebinding | high | CORS config enables rebinding | | unsafe-string-comparison | timing-attacks | medium | Timing-vulnerable comparison | | early-return-auth | timing-attacks | low | Early return in auth function | | high-resolution-timing | timing-attacks | info | High-resolution timing API usage | | array-length-timing | timing-attacks | low | Array iteration timing leak | | missing-vary-header | cache-poisoning | medium | Missing Vary header on dynamic content | | unkeyed-header-reflection | cache-poisoning | high | Unkeyed header reflected in response | | cache-control-misconfigured | cache-poisoning | medium | Cache-Control misconfiguration | | parameter-cache-reflection | cache-poisoning | high | URL param reflected in cached response | | fat-get-request | cache-poisoning | low | GET request with body (fat GET) | | cloud-resource-unclaimed | subdomain-takeover | high | Reference to claimable cloud resource | | dangling-cname-js | subdomain-takeover | medium | Dangling CNAME in JavaScript | | broken-external-resource | subdomain-takeover | high | Broken external resource reference | | mail-subdomain-reference | subdomain-takeover | medium | Mail service subdomain reference | | cors-wildcard-credentials | cors | critical | CORS wildcard with credentials | | cors-null-origin | cors | high | CORS accepts null origin | | cors-origin-reflection | cors | high | CORS reflects origin header | | cors-preflight-permissive | cors | medium | Overly permissive CORS preflight | | cors-internal-access | cors | high | CORS request to internal network | | content-length-manipulation | request-smuggling | high | Content-Length header manipulation | | transfer-encoding-usage | request-smuggling | medium | Transfer-Encoding header usage | | ambiguous-request | request-smuggling | medium | Ambiguous HTTP request pattern | | http2-downgrade | request-smuggling | low | HTTP/2 downgrade pattern | | json-parse-reviver | deserialization | medium | JSON.parse with reviver function | | function-constructor-deser | deserialization | high | Function constructor deserialization | | eval-deserialization | deserialization | critical | Eval-based deserialization | | jquery-deser-methods | deserialization | medium | jQuery unsafe deserialization | | postmessage-deser | deserialization | high | PostMessage data deserialization | | toctou-pattern | race-condition | medium | Time-of-check-time-of-use pattern | | unsync-shared-state | race-condition | medium | Unsynchronized shared state | | double-submit-risk | race-condition | low | Double submit vulnerability | | parallel-fetch-race | race-condition | low | Parallel fetch race condition | | localstorage-race | race-condition | low | LocalStorage race condition | | missing-permissions-policy | permissions-policy | medium | Missing Permissions Policy | | iframe-allow-permissive | permissions-policy | medium | Overly permissive iframe allow | | autoplay-without-policy | permissions-policy | low | Autoplay without policy | | sensor-access | permissions-policy | low | Motion sensor access | | document-domain-usage | permissions-policy | high | document.domain usage | | math-random-security | crypto-weaknesses | high | Math.random for security | | hardcoded-crypto-secrets | crypto-weaknesses | critical | Hardcoded cryptographic secrets | | deprecated-crypto-api | crypto-weaknesses | high | Deprecated crypto API usage | | weak-password-hashing | crypto-weaknesses | high | Weak password hashing | | crypto-subtle-validation | crypto-weaknesses | medium | Web Crypto API issues | | catastrophic-backtracking | redos | high | Catastrophic backtracking regex | | unbounded-regex-input | redos | high | Unbounded regex on user input | | email-regex-redos | redos | medium | Vulnerable email regex | | url-regex-redos | redos | medium | Vulnerable URL regex | | regex-from-input | redos | critical | Regex created from user input | | internal-package-reference | dependency-confusion | medium | Internal package name reference | | uncommon-cdn-package | dependency-confusion | low | Uncommon CDN package | | dynamic-import-risk | dependency-confusion | medium | Dynamic import risk | | scoped-package-risk | dependency-confusion | high | Scoped package without registry lock | | url-param-to-fetch | ssrf | high | URL parameter passed to fetch | | user-input-url | ssrf | high | User input in URL construction | | proxy-endpoint-usage | ssrf | medium | Proxy endpoint usage | | webhook-config | ssrf | medium | Webhook configuration interface | | media-url-from-input | ssrf | medium | Media URL from user input | | window-opener-access | tabnabbing | high | window.opener access | | window-open-no-noopener | tabnabbing | medium | window.open without noopener | | cross-origin-frame-access | tabnabbing | medium | Cross-origin frame access | | postmessage-to-opener | tabnabbing | medium | postMessage to opener | | sensitive-data-new-tab | tabnabbing | low | Sensitive data in new tab link | | polyglot-file-pattern | content-type | medium | Polyglot file pattern | | jsonp-endpoint | content-type | medium | JSONP endpoint | | data-url-executable | content-type | high | Executable data URL | | blob-url-xss | content-type | medium | Blob URL XSS risk | | mime-sniffing-risk | content-type | low | MIME sniffing risk |

HTML Report Features

The interactive HTML report includes:

  • Summary Cards - Click to filter by All/Passed/Failed/Critical
  • Category Filters - Toggle visibility by rule category
  • Hide/Show Rules - Hide individual rules, restore from dropdown
  • Remediation - How-to-fix guidance for each violation
  • View Source - See full element HTML with DevTools tips

Reporter Integration

QAstell provides a flexible architecture for integrating security results into various test reporters.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                        AuditResults                              │
│                  (from SecurityAuditor.audit())                  │
└─────────────────────────────┬───────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                       ReportConnector                            │
│                   (unified attachment API)                       │
└──────────┬──────────────────┴───────────────────┬───────────────┘
           │                                      │
           ▼                                      ▼
┌──────────────────────┐              ┌──────────────────────────┐
│     Formatters       │              │       Adapters           │
│  (output generation) │              │  (reporter integration)  │
├──────────────────────┤              ├──────────────────────────┤
│ • html               │              │ • allure (Allure 2/3)    │
│ • htmlSummary        │              │ • playwright (testInfo)  │
│ • markdown           │              │ • cucumber (World)       │
│ • json (Enterprise+) │              │ • file (save to disk)    │
│ • sarif (Corporate)  │              └──────────────────────────┘
│ • junit              │
│ • cucumber           │
└──────────────────────┘

Quick Start with ReportConnector

import { SecurityAuditor } from 'qastell';
import { ReportConnector, adapters } from 'qastell/reporters';

// 1. Create adapter for your reporter
const adapter = adapters.allure(allure);

// 2. Create connector
const connector = new ReportConnector(adapter);

// 3. Run audit and attach results
const results = await auditor.audit();
await connector.attach(results, {
  inline: 'markdown',        // Show markdown summary inline
  attachments: ['html'],     // Attach full HTML report
});

Available Adapters

| Adapter | Usage | Description | |---------|-------|-------------| | adapters.allure(allure) | Allure 2/3 | Labels, descriptions, attachments | | adapters.playwright(testInfo) | Playwright | Test attachments | | adapters.cucumber(world) | Cucumber | Scenario embeddings | | adapters.file(outputDir) | Any | Save reports to disk |

Legacy Connectors

The legacy connector classes (PlaywrightConnector, AllureConnector, CucumberConnector) are still available for backward compatibility:

import { CucumberConnector } from 'qastell/connectors';

const connector = new CucumberConnector();
await connector.attachSummary(results, this);  // 'this' is Cucumber World

See the examples for complete integration patterns.

CI/CD Integration

GitHub Actions with SARIF

- name: Run security audit
  run: npx playwright test security-audit

- name: Upload SARIF report
  if: always()
  uses: github/codeql-action/upload-sarif@v4
  with:
    sarif_file: reports/security.sarif
    category: qastell
  continue-on-error: true

See it in action — The qastell-community repo runs QAstell in CI against a real demo site with Playwright and Puppeteer. Browse the latest reports on GitHub Pages, or view the workflow source.

GitLab CI

security_audit:
  script:
    - npm run test:security
  artifacts:
    reports:
      sast: reports/security.sarif

Licensing

QAstell offers three tiers:

| Tier | Scans/Day | Report Formats | Price | |------|-----------|----------------|-------| | Free (Non-Commercial) | 10 | HTML | €0 | | Enterprise | 100 | HTML, JSON | €99/month | | Corporate | Unlimited | HTML, JSON, SARIF | €499/month |

Note: The free tier is for non-commercial use only (personal projects, open source, learning). For commercial use, please purchase an Enterprise or Corporate license.

Using a License Key

import { initLicense, SecurityAuditor } from 'qastell';

// Initialize once at startup
initLicense(process.env.QASTELL_LICENSE);

// Then use normally - quota is tracked automatically
const auditor = new SecurityAuditor(page);
await auditor.audit();

Check License Status

# Check current license status
npx qastell license-info

# Check a specific license key
QASTELL_LICENSE="..." npx qastell license-info

API Reference

SecurityAuditor

constructor(page: Page, options?: SecurityAuditorOptions)

Methods

  • audit(config?: AuditConfig): Promise<AuditResults> - Run security audit
  • assertNoViolations(options?: AssertionOptions): Promise<void> - Assert no violations (throws on failure)
  • getFramework(): string - Get detected framework name

AuditResults

  • raw: AuditResult - Raw audit data
  • violations - All violations with rule metadata
  • summary - Counts by severity and category
  • passed(): boolean - Check if audit passed thresholds
  • toJSON(): string - Generate JSON report (Enterprise+)
  • toHTML(): string - Generate full interactive HTML report
  • toSummaryHTML(): string - Generate compact summary HTML (smaller, faster)
  • toSARIF(): string - Generate SARIF report (Corporate only)
  • attachReport(testInfo, name?) - Attach HTML report to Playwright test (appears in HTML reporter)

Types

interface AuditConfig {
  include?: RuleCategory[];
  exclude?: RuleCategory[];
  skipRules?: string[];
  thresholds?: Partial<Record<CVSSSeverity, number>>;
}

type CVSSSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';

Learn More

License

See qastell.eu/terms for details. QAstell is free for non-commercial use. Commercial use requires a paid license.


Made in the 🇪🇺 with ❤️ for people, environment, and diversity.