PreviewShield
Stop a pull request from quietly weakening your web security.
PreviewShield is a policy-as-code scanner and CI regression gate for web deployments. It checks security headers, cookies, CORS, redirects, TLS, and response health, then compares a pull-request preview with production. Existing production debt stays visible, while the gate can fail only on findings that are new or more severe.
PreviewShield diff: FAIL
Baseline: https://example.com (A, 91/100)
Preview: https://preview.example.dev (B, 83/100)
Failure threshold: high
Changes: 1 regressions, 0 resolved, 0 changed, 18 unchanged
REGRESSION (1):
[HIGH] PS1101 - Enforced Content-Security-Policy is missing
Why PreviewShield?
- Purpose-built preview diffs. Match findings by rule, route, and subject across different hostnames, so production and ephemeral deployments compare cleanly.
- Policy that lives with the code. Choose a profile, scan multiple routes, override severities, disable accepted rules, and require organization-specific headers in YAML.
- CI-native outputs. Render console, JSON, Markdown, SARIF 2.1.0, JUnit XML, or a standalone HTML report from the same scan.
- A local release control room. Open a no-account browser interface for guided scans, deployment comparisons, finding filters, and report downloads.
- Safe network defaults. Block non-public addresses, validate every redirect, pin connections to validated DNS answers, preserve TLS hostname verification, and avoid environment proxies.
- Actionable checks. Every finding has a stable rule ID, severity, evidence, remediation, and reference link.
- Small and portable. Python 3.10+ with one runtime dependency, PyYAML. Response bodies are not downloaded.
flowchart LR
P["Production"] --> S1["Scan selected routes"]
V["PR preview"] --> S2["Scan selected routes"]
Y[".previewshield.yml"] --> S1
Y --> S2
S1 --> D["Match rule + route + subject"]
S2 --> D
D --> G{"New or severity increased at threshold?"}
Y --> G
G --> R["Console / JSON / Markdown / SARIF / JUnit / HTML"]
Quick start
Install a released version from PyPI:
python -m pip install previewshield
Until the first PyPI release, install directly from the repository:
python -m pip install "git+https://github.com/devUmut35/PreviewShield.git"
Prefer a browser? Launch the local-only interface:
previewshield ui
PreviewShield opens a guided release control room on 127.0.0.1. It supports single-site scans,
production-to-preview comparisons, result filtering, and HTML, JSON, Markdown, SARIF, or JUnit
downloads without an account or hosted scanning service. See the web UI guide.
Scan one deployment:
previewshield scan https://example.com
Compare production with a pull-request preview and fail on high or critical regressions:
previewshield diff \
--baseline https://example.com \
--preview https://pr-142.example.dev \
--fail-on high
PreviewShield returns 0 when the policy passes and 1 when the configured threshold is
crossed, making the command a drop-in CI gate.
Add it to a pull request
Run this job after your preview deployment. Replace vars.PREVIEW_URL with the URL produced by
your deployment provider.
name: Preview security
on:
pull_request:
permissions:
contents: read
jobs:
previewshield:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Compare preview with production
id: previewshield
uses: devUmut35/PreviewShield@v1
with:
baseline: https://example.com
preview: ${{ vars.PREVIEW_URL }}
config: .previewshield.yml
paths: |
/
/login
/api/health
fail-on: high
format: sarif
output: previewshield.sarif
The Action writes a job summary, generates JSON, Markdown, and SARIF sidecars, and exposes
report, score, grade, and passed outputs. See the
GitHub Action guide for artifact and code-scanning examples.
Put the policy in your repository
Generate and validate a starter policy:
previewshield init
previewshield policy validate .previewshield.yml
version: 1
name: public-web
profile: balanced
fail_on: high
paths:
- /
- /login
- /api/health
network:
timeout_seconds: 10
max_redirects: 5
allow_private: false
allowed_hosts:
- example.com
- "*.example.dev"
checks:
min_hsts_max_age: 15552000
certificate_warning_days: 30
disabled: []
severity_overrides:
PS1204: medium
required_headers:
X-Robots-Tag:
severity: medium
contains: noindex
diff:
mode: regressions
Unknown keys and invalid values are rejected instead of being silently ignored. Read the
policy reference for every option and the difference between
regressions and absolute modes.
Useful commands
# Scan several routes
previewshield scan example.com --path / --path /login --path /api/health
# Produce a human report and CI sidecars in one request
previewshield scan example.com \
--format html --output report.html \
--also-format sarif=report.sarif \
--also-format junit=report.xml
# Use a request header for an authenticated preview; values are not written to reports
previewshield scan preview.example.dev \
--header "Authorization: Bearer $PREVIEW_TOKEN"
# Inspect stable rule metadata
previewshield rules
previewshield rules --json
# Open the local browser interface without launching a new browser tab
previewshield ui --no-open
The command surface also includes diff, init, and policy validate. Run
previewshield COMMAND --help for all options.
What it checks
PreviewShield currently ships 30 stable rules across:
| Area | Examples |
|---|---|
| Transport and TLS | HTTPS, redirect downgrade, negotiated TLS, cipher, certificate expiry |
| Browser hardening | HSTS, CSP, clickjacking, MIME sniffing, referrer and permissions policies |
| Cross-origin policy | Wildcard or opaque origins, credentialed CORS, missing Vary: Origin |
| Cookies | Secure, HttpOnly, SameSite, __Host- and __Secure- prefix contracts |
| Response health | Client/server errors and exposed technology headers |
| Project policy | Required response headers and project-specific severity decisions |
See the rule catalog for IDs, default severities, and remediation intent.
Reports and automation
| Format | Best for |
|---|---|
console |
Local terminal feedback |
json |
Automation and long-term storage |
markdown |
Job summaries and pull-request comments |
sarif |
GitHub code scanning and SARIF-compatible platforms |
junit |
CI test-report viewers |
html |
Shareable, standalone human reports |
Output is consistently structured, sorted, and sanitized: credentials, query strings, fragments, cookie-like fields, and common token patterns are redacted. Details are in the output guide.
Security model
Scanning URLs from CI creates an SSRF boundary. PreviewShield treats it as one:
- Only HTTP(S) targets without URL credentials are accepted.
- Every hostname, including every redirect destination, is checked against the optional host allowlist, then resolved and validated.
- Non-public, loopback, link-local, reserved, multicast, and unspecified addresses are blocked by default.
- The socket connects to the exact validated address while HTTPS still uses normal hostname verification and SNI.
- All caller-supplied headers are removed on cross-origin redirects, environment proxies are ignored, and response bodies are never read.
The optional browser UI binds only to 127.0.0.1 and requires an exact Host, same-origin POST,
HttpOnly session cookie, and CSRF token. Private targets remain locked unless the user starts that
session with previewshield ui --allow-private-targets and confirms authorization in the UI.
--allow-private deliberately relaxes the network boundary and should be used only for trusted
local test targets. PreviewShield is a hardening auditor, not a vulnerability scanner or proof
that a site is secure. Read the complete security model and only scan
systems you own or are authorized to test.
Documentation
- Getting started
- Local web interface
- Policy reference
- GitHub Action
- Output formats
- Rule catalog
- Security model
- Python API
- Migrating from Security Header Auditor
Contributing
PreviewShield is Apache-2.0 licensed and built in the open. Bug reports, rule proposals, reporter integrations, tests, documentation, and security review are welcome. Start with CONTRIBUTING.md, browse good first issues, or open a focused feature request.
If PreviewShield protects one of your releases, consider starring the repository. It helps other teams discover a practical security regression gate.
Security vulnerabilities should be reported privately according to SECURITY.md. General support expectations are documented in SUPPORT.md.
License
Copyright 2026 Umutcan Altan. Licensed under the Apache License 2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file previewshield-1.0.0.tar.gz.
File metadata
- Download URL: previewshield-1.0.0.tar.gz
- Upload date:
- Size: 204.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b842486b464cbfe18ac66d96488d7544ae39012a42d7b20158290a347f644bc4
|
|
| MD5 |
952c6221ab5ad51580aa9a3baac391d4
|
|
| BLAKE2b-256 |
22d89c9bdb2ea72f28cef65901044a5f3b210bf87f9f7223a049ed35a253207a
|
Provenance
The following attestation bundles were made for previewshield-1.0.0.tar.gz:
Publisher:
release.yml on devUmut35/PreviewShield
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
previewshield-1.0.0.tar.gz -
Subject digest:
b842486b464cbfe18ac66d96488d7544ae39012a42d7b20158290a347f644bc4 - Sigstore transparency entry: 2218776627
- Sigstore integration time:
-
Permalink:
devUmut35/PreviewShield@cfd19cc340bc9bd97f1f72d049bdde2c3f613cdd -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/devUmut35
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@cfd19cc340bc9bd97f1f72d049bdde2c3f613cdd -
Trigger Event:
release
-
Statement type:
File details
Details for the file previewshield-1.0.0-py3-none-any.whl.
File metadata
- Download URL: previewshield-1.0.0-py3-none-any.whl
- Upload date:
- Size: 190.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30ce2055fad1fe0677034b0631dea941728e6d2d73117929b4564576e308441e
|
|
| MD5 |
afe43c802d7a0186a4bc249230483ccd
|
|
| BLAKE2b-256 |
25187b7775cd1dacd2f95eb60d29d80d84e394cd69d2bddcfbe5efdb22e5948d
|
Provenance
The following attestation bundles were made for previewshield-1.0.0-py3-none-any.whl:
Publisher:
release.yml on devUmut35/PreviewShield
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
previewshield-1.0.0-py3-none-any.whl -
Subject digest:
30ce2055fad1fe0677034b0631dea941728e6d2d73117929b4564576e308441e - Sigstore transparency entry: 2218776858
- Sigstore integration time:
-
Permalink:
devUmut35/PreviewShield@cfd19cc340bc9bd97f1f72d049bdde2c3f613cdd -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/devUmut35
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@cfd19cc340bc9bd97f1f72d049bdde2c3f613cdd -
Trigger Event:
release
-
Statement type: