Repository: projectdiscovery/katana
Branch: dev
Commit: 2f9ecf4211b1
Files: 156
Total size: 1.2 MB
Directory structure:
gitextract_rfj92yh6/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── README.md
│ │ ├── config.yml
│ │ ├── feature_request.md.disabled
│ │ └── issue-report.md.disabled
│ ├── MAINTAINER_GUIDE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ ├── release.yml
│ └── workflows/
│ ├── build-test.yml
│ ├── codeql-analysis.yml
│ ├── compat-checks.yaml
│ ├── dep-auto-merge.yml
│ ├── discussion-triage.yml
│ ├── dockerhub-push.yml
│ ├── functional-test.yml
│ ├── release-binary.yml
│ ├── release-test.yml
│ ├── security-crawl-maze-score.yaml
│ ├── stale.yml
│ └── workflow-monitor.yml
├── .gitignore
├── .goreleaser/
│ ├── linux.yml
│ ├── mac.yml
│ └── windows.yml
├── Dockerfile
├── LICENSE.md
├── Makefile
├── README.md
├── SECURITY.md
├── cmd/
│ ├── functional-test/
│ │ ├── main.go
│ │ └── run.sh
│ ├── integration-test/
│ │ ├── filters.go
│ │ ├── integration-test.go
│ │ └── library.go
│ └── tools/
│ └── crawl-maze-score/
│ └── main.go
├── go.mod
├── go.sum
├── integration_tests/
│ └── run.sh
├── internal/
│ ├── runner/
│ │ ├── banner.go
│ │ ├── executer.go
│ │ ├── healthcheck.go
│ │ ├── options.go
│ │ └── runner.go
│ └── testutils/
│ ├── helper.go
│ ├── integration.go
│ └── testutils.go
└── pkg/
├── engine/
│ ├── common/
│ │ ├── base.go
│ │ ├── error.go
│ │ └── http.go
│ ├── engine.go
│ ├── headless/
│ │ ├── TODOS.md
│ │ ├── browser/
│ │ │ ├── browser.go
│ │ │ ├── cookie/
│ │ │ │ ├── cookie.go
│ │ │ │ ├── cookie_test.go
│ │ │ │ └── rules.json
│ │ │ ├── element.go
│ │ │ └── stealth/
│ │ │ └── assets.go
│ │ ├── captcha/
│ │ │ ├── capsolver/
│ │ │ │ ├── capsolver.go
│ │ │ │ └── capsolver_test.go
│ │ │ ├── captcha.go
│ │ │ ├── helpers_test.go
│ │ │ ├── identify.go
│ │ │ ├── identify_test.go
│ │ │ ├── inject_test.go
│ │ │ ├── injection_test.go
│ │ │ ├── integration_test.go
│ │ │ ├── js/
│ │ │ │ ├── identify.js
│ │ │ │ ├── inject-hcaptcha.js
│ │ │ │ ├── inject-recaptcha.js
│ │ │ │ ├── inject-turnstile.js
│ │ │ │ └── js.go
│ │ │ ├── solver.go
│ │ │ └── solver_test.go
│ │ ├── crawler/
│ │ │ ├── crawler.go
│ │ │ ├── diagnostics/
│ │ │ │ └── diagnostics.go
│ │ │ ├── formfill.go
│ │ │ ├── normalizer/
│ │ │ │ ├── dom_utils.go
│ │ │ │ ├── dom_utils_test.go
│ │ │ │ ├── helpers.go
│ │ │ │ ├── normalizer.go
│ │ │ │ ├── simhash/
│ │ │ │ │ ├── simhash.go
│ │ │ │ │ └── simhash_test.go
│ │ │ │ ├── text_utils.go
│ │ │ │ └── text_utils_test.go
│ │ │ ├── state.go
│ │ │ └── state_test.go
│ │ ├── debugger.go
│ │ ├── graph/
│ │ │ └── graph.go
│ │ ├── headless.go
│ │ ├── js/
│ │ │ ├── js.go
│ │ │ ├── page-init.js
│ │ │ └── utils.js
│ │ └── types/
│ │ └── types.go
│ ├── hybrid/
│ │ ├── crawl.go
│ │ ├── doc.go
│ │ ├── hijack.go
│ │ └── hybrid.go
│ ├── parser/
│ │ ├── files/
│ │ │ ├── request.go
│ │ │ ├── robotstxt.go
│ │ │ ├── robotstxt_test.go
│ │ │ ├── sitemapxml.go
│ │ │ └── sitemapxml_test.go
│ │ ├── parser.go
│ │ ├── parser_generic.go
│ │ ├── parser_nojs.go
│ │ └── parser_test.go
│ └── standard/
│ ├── crawl.go
│ ├── doc.go
│ └── standard.go
├── navigation/
│ ├── request.go
│ └── response.go
├── output/
│ ├── custom_field.go
│ ├── error.go
│ ├── fields.go
│ ├── fields_test.go
│ ├── file_writer.go
│ ├── format_json.go
│ ├── format_screen.go
│ ├── format_template.go
│ ├── options.go
│ ├── output.go
│ ├── responses.go
│ └── result.go
├── types/
│ ├── crawler_options.go
│ ├── default.go
│ ├── options.go
│ └── options_test.go
└── utils/
├── extensions/
│ ├── extensions.go
│ └── extensions_test.go
├── filters/
│ ├── filters.go
│ ├── filters_test.go
│ └── simple.go
├── formfields.go
├── formfields_test.go
├── formfill.go
├── formfill_test.go
├── jsluice.go
├── jsluice_test.go
├── maps.go
├── maps_test.go
├── pathtrie.go
├── pathtrie_test.go
├── queue/
│ ├── priority_queue.go
│ ├── priority_queue_test.go
│ ├── queue.go
│ ├── stack.go
│ ├── stack_test.go
│ └── strategy.go
├── regex.go
├── regex_test.go
├── scope/
│ ├── scope.go
│ └── scope_test.go
├── urlfingerprint.go
├── urlfingerprint_test.go
├── utils.go
└── utils_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/README.md
================================================
# Issue Template Management
## New Issue Workflow (Discussion-First)
To improve issue triage and reduce noise from questions being filed as bugs, all issue creation now goes through GitHub Discussions first.
### For Users:
**❌ Direct issue creation is disabled**
**✅ All reports must start as discussions**
Users will be redirected to:
- 🐛 **Bug Reports** → [Q&A Discussions](https://github.com/projectdiscovery/katana/discussions/new?category=q-a)
- 💡 **Feature Requests** → [Ideas Discussions](https://github.com/projectdiscovery/katana/discussions/new?category=ideas)
- ❓ **Questions** → [Q&A Discussions](https://github.com/projectdiscovery/katana/discussions/new?category=q-a)
### For Maintainers:
#### Converting Discussions to Issues:
1. **Review the discussion** thoroughly
2. **Determine if it's a valid bug/feature** (not just a question)
3. **Convert to issue** using GitHub's "Convert to Issue" feature:
- Go to the discussion
- Click "⋯" menu → "Convert to issue"
- Add appropriate labels and assignees
#### Triage Guidelines:
**Convert to Issue:**
- ✅ Confirmed bugs with reproduction steps
- ✅ Well-defined feature requests with clear use cases
- ✅ Security vulnerabilities (after initial assessment)
**Keep as Discussion:**
- ❌ Usage questions ("How do I...?")
- ❌ Configuration help
- ❌ Unclear or incomplete bug reports
- ❌ Feature ideas that need more discussion/refinement
### Benefits:
- 📊 **Better issue quality** - Only confirmed bugs/features become issues
- 🎯 **Easier triage** - Questions don't clutter the issue tracker
- 💬 **Community involvement** - Discussion encourages collaboration before formal issues
- 🧹 **Cleaner issue tracker** - Focus on actionable items only
## Re-enabling Templates (If Needed)
If you need to temporarily re-enable direct issue creation:
```bash
# Re-enable templates
mv issue-report.md.disabled issue-report.md
mv feature_request.md.disabled feature_request.md
# Update config.yml to add them back
```
## Template Files:
- `config.yml` - Main configuration (redirects all to discussions)
- `issue-report.md.disabled` - Bug report template (disabled)
- `feature_request.md.disabled` - Feature request template (disabled)
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Report a Bug (Discussion First)
url: https://github.com/projectdiscovery/katana/discussions/new?category=q-a
about: Report bugs or issues via discussion for proper triage before issue creation
- name: Feature Request (Discussion First)
url: https://github.com/projectdiscovery/katana/discussions/new?category=ideas
about: Share feature ideas via discussion for evaluation before implementation
- name: Ask a Question
url: https://github.com/projectdiscovery/katana/discussions/new?category=q-a
about: Ask questions about usage, configuration, or troubleshooting
- name: General Discussion
url: https://github.com/projectdiscovery/katana/discussions/new?category=general
about: General discussion about the project
- name: Security Issues
url: mailto:security@projectdiscovery.io
about: Report security vulnerabilities privately via email
- name: Discord Community
url: https://discord.gg/projectdiscovery
about: Connect with PD Team and community for real-time discussion
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md.disabled
================================================
---
name: Feature request
about: Request feature to implement in this project
labels: 'Type: Enhancement'
---
### Please describe your feature request:
### Describe the use case of this feature:
================================================
FILE: .github/ISSUE_TEMPLATE/issue-report.md.disabled
================================================
---
name: Issue report
about: Create a report to help us to improve the project
labels: 'Type: Bug'
---
### katana version:
### Current Behavior:
### Expected Behavior:
### Steps To Reproduce:
### Anything else:
================================================
FILE: .github/MAINTAINER_GUIDE.md
================================================
# Maintainer Guide: Discussion-First Issue Management
## Overview
Katana now uses a **discussion-first approach** for issue management to improve triage quality and reduce noise from questions being filed as bugs.
## How It Works
### 1. **All Reports Start as Discussions**
- Users cannot create issues directly
- All bug reports → **Q&A Discussions**
- All feature requests → **Ideas Discussions**
- All questions → **Q&A Discussions**
### 2. **Automated Triage Helper**
- Auto-responds to discussions with helpful guidance
- Auto-flags potential bugs with keywords detection
- Provides checklists for proper bug reporting
### 3. **Maintainer Conversion Process**
- Review discussions for completeness
- Convert valid issues using GitHub's built-in feature
- Apply appropriate labels during conversion
## Conversion Guidelines
### 🐛 **Bug Reports** → Convert to Issue When:
**Well-Defined Problems:**
- Clear reproduction steps provided
- Katana version specified
- Expected vs actual behavior described
- Environment details included
- Error messages/logs included
**Confirmed Bugs:**
- Issue reproduced by maintainer or community
- Not a configuration/usage question
- Not working as designed
**Keep as Discussion:**
- Incomplete information
- Usage questions ("How do I...?")
- Configuration problems
- Working as intended
### 💡 **Feature Requests** → Convert to Issue When:
**Solid Proposals:**
- Clear use case defined
- Benefits to community explained
- Implementation approach considered
- Not easily achievable with existing features
**Community Support:**
- Multiple users expressing interest
- Maintainer approval for implementation
- Fits project roadmap
**Keep as Discussion:**
- Vague ideas needing refinement
- Better suited as external tools/plugins
- Conflicts with project goals
- Needs more community input
## Conversion Process
### Using GitHub's Convert Feature:
1. **Open the discussion**
2. **Click the "⋯" menu** (top right)
3. **Select "Convert to issue"**
4. **Choose repository** (same repo)
5. **Review title/body** - edit if needed
6. **Add labels:**
- `Type: Bug` for confirmed bugs
- `Type: Enhancement` for approved features
- `Priority: High/Medium/Low` as appropriate
- `Component: Engine/Parser/Output` etc.
### Template for Converted Issues:
When converting, consider adding this note:
```markdown
**Converted from Discussion:** #[discussion_number]
[Original discussion content here]
---
**Maintainer Notes:**
- [ ] Issue confirmed through discussion
- [ ] Reproduction steps verified
- [ ] Ready for implementation/investigation
```
## Workflow Benefits
### **For Project Health:**
- **Cleaner issue tracker** - Only actionable items
- **Better metrics** - Issues vs discussions clearly separated
- **Faster resolution** - Less time sorting questions from bugs
### **For Community:**
- **Inclusive discussions** - Everyone can participate in triage
- **Better help** - Community can answer questions quickly
- **Learning opportunity** - Users see resolution process
### **For Maintainers:**
- **Pre-filtered issues** - Only valid bugs/features reach issue tracker
- **Rich context** - Discussion history provides background
- **Community input** - Others help validate before conversion
## Examples
### **Good Bug Discussion → Issue Conversion**
**Discussion Title:** "Katana crashes when using -hl with custom headers"
**Discussion Body:**
- Katana version: v1.2.1
- Command: `katana -u example.com -hl -H "Custom: value"`
- Error: panic in hybrid engine
- Platform: macOS 14.1
- Reproduction: consistent crash
**→ Convert to Issue:** Clear bug with reproduction steps
### **Question → Keep as Discussion**
**Discussion Title:** "How to crawl only PDF files?"
**Discussion Body:**
- New user question
- Asking for usage help
- Not a bug or feature request
**→ Keep as Discussion:** Usage question, answer in discussion
### **Needs More Info → Keep as Discussion**
**Discussion Title:** "Katana doesn't work"
**Discussion Body:**
- Vague description
- No version, command, or error details
- No reproduction steps
**→ Keep as Discussion:** Request more information first
## Quick Reference
| Type | Action | Labels for Conversion |
|------|---------|-------------------|
| **Confirmed Bug** | Convert → Issue | `Type: Bug`, `Priority: [level]` |
| **Approved Feature** | Convert → Issue | `Type: Enhancement`, `Priority: [level]` |
| **Usage Question** | Keep → Discussion | N/A |
| **Needs Info** | Keep → Discussion | N/A |
| **Security Issue** | Email → security@projectdiscovery.io | N/A |
This workflow ensures high-quality issues while maintaining an inclusive, helpful community environment!
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Proposed changes
### Proof
## Checklist
- [ ] Pull request is created against the [dev](https://github.com/projectdiscovery/katana/tree/dev) branch
- [ ] All checks passed (lint, unit/integration/regression tests etc.) with my changes
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have added necessary documentation (if appropriate)
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Maintain dependencies for go modules
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
target-branch: "dev"
commit-message:
prefix: "chore"
include: "scope"
labels:
- "Type: Maintenance"
allow:
- dependency-name: "github.com/projectdiscovery/*"
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
target-branch: "dev"
commit-message:
prefix: "chore"
include: "scope"
labels:
- "Type: Maintenance"
# Maintain dependencies for docker
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
target-branch: "dev"
commit-message:
prefix: "chore"
include: "scope"
labels:
- "Type: Maintenance"
================================================
FILE: .github/release.yml
================================================
changelog:
exclude:
authors:
- app/dependabot
- dependabot
categories:
- title: 🎉 New Features
labels:
- "Type: Enhancement"
- title: 🐞 Bug Fixes
labels:
- "Type: Bug"
- title: 🔨 Maintenance
labels:
- "Type: Maintenance"
- title: Other Changes
labels:
- "*"
================================================
FILE: .github/workflows/build-test.yml
================================================
name: 🔨 Build Test
on:
workflow_dispatch:
pull_request:
branches:
- dev
paths:
- '**.go'
- '**.mod'
jobs:
lint:
name: "Lint"
if: "${{ !endsWith(github.actor, '[bot]') }}"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go@v1
- uses: projectdiscovery/actions/golangci-lint/v2@v1
build:
name: Test Builds
needs: [lint]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Set up Go
uses: projectdiscovery/actions/setup/go@v1
- name: Test
run: go test ./...
working-directory: .
- name: Build
run: go build .
working-directory: cmd/katana/
- name: Integration Tests
env:
GH_ACTION: true
run: bash run.sh
working-directory: integration_tests/
- name: Install
run: go install
working-directory: cmd/katana/
- name: Race Condition Tests
run: go build -race .
working-directory: cmd/katana/
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
name: 🚨 CodeQL Analysis
on:
workflow_dispatch:
pull_request:
branches:
- dev
paths:
- '**.go'
- '**.mod'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
steps:
- name: Checkout repository
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
================================================
FILE: .github/workflows/compat-checks.yaml
================================================
name: ♾️ Compatibility Checks
on:
pull_request:
types: [opened, synchronize]
branches:
- dev
jobs:
check:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
================================================
FILE: .github/workflows/dep-auto-merge.yml
================================================
name: 🤖 Auto Merge
on:
pull_request_review:
types: [submitted]
workflow_run:
workflows: ["♾️ Compatibility Check"]
types:
- completed
permissions:
pull-requests: write
issues: write
repository-projects: write
jobs:
auto-merge:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- uses: actions/checkout@v6
with:
token: ${{ secrets.DEPENDABOT_PAT }}
- uses: ahmadnassri/action-dependabot-auto-merge@v2
with:
github-token: ${{ secrets.DEPENDABOT_PAT }}
target: all
================================================
FILE: .github/workflows/discussion-triage.yml
================================================
name: Discussion Triage Helper
on:
discussion:
types: [created]
permissions:
discussions: write
jobs:
triage:
runs-on: ubuntu-latest
steps:
- name: Add initial response to discussions
uses: actions/github-script@v8
with:
script: |
const { discussion } = context.payload;
// Add helpful response based on discussion category
let response = "";
if (discussion.category.slug === "q-a") {
if (discussion.title.toLowerCase().includes("bug") ||
discussion.title.toLowerCase().includes("error") ||
discussion.title.toLowerCase().includes("crash") ||
discussion.title.toLowerCase().includes("fail")) {
response = "Thanks for the discussion! This looks like it might be a bug report.\n\n" +
"**For Maintainers:** If this is confirmed to be a bug with proper reproduction steps, consider converting to an issue using \"Convert to issue\" in the discussion menu.\n\n" +
"**For the Reporter:** To help us triage this effectively, please provide:\n" +
"- [ ] Katana version (`katana -version`)\n" +
"- [ ] Complete command that reproduces the issue\n" +
"- [ ] Expected vs actual behavior\n" +
"- [ ] Operating system and environment details\n" +
"- [ ] Any error messages or logs\n\n" +
"This helps us determine if this should become a tracked issue. Thank you!";
} else {
response = "Thanks for your question!\n\n" +
"The community and maintainers will help answer this. If this turns out to be a bug or feature request after discussion, a maintainer can convert it to a tracked issue.\n\n" +
"**Tip:** For faster responses, you can also join our [Discord server](https://discord.gg/projectdiscovery) for real-time help!";
}
}
else if (discussion.category.slug === "ideas") {
response = "Thanks for sharing this idea!\n\n" +
"**For Maintainers:** If this feature request is well-defined and approved for implementation, consider converting to an issue using \"Convert to issue\" in the discussion menu.\n\n" +
"**For the Reporter:** To help evaluate this feature:\n" +
"- [ ] Describe the specific use case this would solve\n" +
"- [ ] Explain how this would benefit the katana user community\n" +
"- [ ] Consider if this could be implemented as a plugin or external tool\n" +
"- [ ] Check if similar functionality already exists\n\n" +
"Well-defined features with clear benefits may be converted to tracked issues for implementation. Thanks for contributing!";
}
if (response) {
await github.rest.discussions.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
discussion_number: discussion.number,
body: response
});
}
- name: Auto-label potential bug reports
uses: actions/github-script@v8
with:
script: |
const { discussion } = context.payload;
const title = discussion.title.toLowerCase();
const body = discussion.body.toLowerCase();
// Check for keywords that suggest this might be a bug
const bugKeywords = [
"bug", "error", "crash", "fail", "broken", "issue", "problem",
"exception", "panic", "segfault", "hang", "freeze", "incorrect"
];
const hasBugKeywords = bugKeywords.some(keyword =>
title.includes(keyword) || body.includes(keyword)
);
if (hasBugKeywords && discussion.category.slug === "q-a") {
// Add a comment suggesting this might need to be converted to an issue
const detectedKeywords = bugKeywords.filter(k => title.includes(k) || body.includes(k));
await github.rest.discussions.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
discussion_number: discussion.number,
body: "**Maintainer Note:** This discussion contains potential bug keywords and may need to be converted to a tracked issue after confirmation.\n\n" +
"**Keywords detected:** " + detectedKeywords.join(', ')
});
}
================================================
FILE: .github/workflows/dockerhub-push.yml
================================================
name: 🌥 Docker Push
on:
workflow_run:
workflows: ["🎉 Release Binary"]
types:
- completed
workflow_dispatch:
jobs:
docker:
runs-on: ubuntu-latest-16-cores
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Get Github tag
id: meta
run: |
curl --silent "https://api.github.com/repos/projectdiscovery/katana/releases/latest" | jq -r .tag_name | xargs -I {} echo TAG={} >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: projectdiscovery/katana:latest,projectdiscovery/katana:${{ steps.meta.outputs.TAG }}
================================================
FILE: .github/workflows/functional-test.yml
================================================
name: 🧪 Functional Test
on:
pull_request:
paths:
- '**.go'
- '**.mod'
workflow_dispatch:
jobs:
functional:
name: Functional Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Set up Go
uses: projectdiscovery/actions/setup/go@v1
- name: Functional Tests
run: |
chmod +x run.sh
bash run.sh ${{ matrix.os }}
working-directory: cmd/functional-test
================================================
FILE: .github/workflows/release-binary.yml
================================================
name: 🎉 Release Binary
on:
push:
tags:
- v*
workflow_dispatch:
jobs:
build-mac:
runs-on: macos-latest
steps:
- name: "Check out code"
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: "Set up Go"
uses: projectdiscovery/actions/setup/go@v1
- name: "Create release on GitHub"
uses: goreleaser/goreleaser-action@v7
with:
args: "release -f .goreleaser/mac.yml --clean"
version: latest
workdir: .
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
build-windows:
runs-on: windows-latest-8-cores
steps:
- name: "Check out code"
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: "Set up Go"
uses: projectdiscovery/actions/setup/go@v1
- name: "Create release on GitHub"
uses: goreleaser/goreleaser-action@v7
with:
args: "release -f .goreleaser/windows.yml --clean"
version: latest
workdir: .
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
build-linux:
runs-on: ubuntu-latest-16-cores
steps:
- name: "Check out code"
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: "Set up Go"
uses: projectdiscovery/actions/setup/go@v1
# todo: musl compatible?
- name: Install Dependences
run: sudo apt install gcc-aarch64-linux-gnu
- name: "Create release on GitHub"
uses: goreleaser/goreleaser-action@v7
with:
args: "release -f .goreleaser/linux.yml --clean"
version: latest
workdir: .
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}"
DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}"
DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}"
================================================
FILE: .github/workflows/release-test.yml
================================================
name: 🔨 Release Test
on:
pull_request:
paths:
- "**.yml"
- "**.go"
- "**.mod"
workflow_dispatch:
jobs:
release-test-mac:
runs-on: macos-latest
steps:
- name: "Check out code"
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Go
uses: projectdiscovery/actions/setup/go@v1
- name: release test
uses: projectdiscovery/actions/goreleaser@v1
with:
args: --config=.goreleaser/mac.yml
workdir: .
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
release-test-linux:
runs-on: ubuntu-latest-16-cores
steps:
- name: "Check out code"
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Go
uses: projectdiscovery/actions/setup/go@v1
# todo: musl compatible?
- name: Install Dependences
run: sudo apt update && sudo apt install gcc-aarch64-linux-gnu
- name: release test
uses: projectdiscovery/actions/goreleaser@v1
with:
args: --config=.goreleaser/linux.yml
workdir: .
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
release-test-windows:
runs-on: windows-latest-8-cores
steps:
- name: "Check out code"
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Go
uses: projectdiscovery/actions/setup/go@v1
- name: release test
uses: projectdiscovery/actions/goreleaser@v1
with:
args: --config=.goreleaser/windows.yml
workdir: .
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
================================================
FILE: .github/workflows/security-crawl-maze-score.yaml
================================================
name: 🙏🏻 Security Crawl Maze Score
on:
workflow_dispatch:
jobs:
build:
name: Run Scoring
runs-on: ubuntu-latest-16-cores
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Set up Go
uses: projectdiscovery/actions/setup/go@v1
- name: Build
run: go build .
working-directory: cmd/katana/
- name: Run Katana Standard
run: ./katana -u https://security-crawl-maze.app/ -kf all -jc -jsluice -d 10 -o output_standard.txt -cos node_modules
working-directory: cmd/katana
- name: Run Katana Headless
run: ./katana -u https://security-crawl-maze.app/ -kf all -jc -jsluice -d 10 -headless -o output_headless.txt -cos node_modules
working-directory: cmd/katana
- name: Run Score
run: go build .; ./crawl-maze-score ../../katana/output_standard.txt ../../katana/output_headless.txt
working-directory: cmd/tools/crawl-maze-score
================================================
FILE: .github/workflows/stale.yml
================================================
name: 💤 Stale
on:
schedule:
- cron: '0 0 * * 0' # Weekly
jobs:
stale:
runs-on: ubuntu-latest
permissions:
actions: write
contents: write # only for delete-branch option
issues: write
pull-requests: write
steps:
- uses: actions/stale@v10
with:
days-before-stale: 90
days-before-close: 7
stale-issue-label: "Status: Stale"
stale-pr-label: "Status: Stale"
stale-issue-message: >
This issue has been automatically marked as stale because it has not
had recent activity. It will be closed in 7 days if no further
activity occurs. Thank you for your contributions!
stale-pr-message: >
This pull request has been automatically marked as stale due to
inactivity. It will be closed in 7 days if no further activity
occurs. Please update if you wish to keep it open.
close-issue-message: >
This issue has been automatically closed due to inactivity. If you
think this is a mistake or would like to continue the discussion,
please comment or feel free to reopen it.
close-pr-message: >
This pull request has been automatically closed due to inactivity.
If you think this is a mistake or would like to continue working on
it, please comment or feel free to reopen it.
close-issue-label: "Status: Abandoned"
close-pr-label: "Status: Abandoned"
exempt-issue-labels: "Status: Abandoned"
exempt-pr-labels: "Status: Abandoned"
================================================
FILE: .github/workflows/workflow-monitor.yml
================================================
name: Workflow Monitor
on:
schedule:
# Run weekly on Mondays at 9 AM UTC
- cron: '0 9 * * 1'
workflow_dispatch: # Allow manual triggering
permissions:
issues: read
discussions: read
jobs:
monitor-workflow:
runs-on: ubuntu-latest
steps:
- name: Check Discussion vs Issue Activity
uses: actions/github-script@v8
with:
script: |
const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
const since = oneWeekAgo.toISOString();
// Get discussions created in the last week
const discussions = await github.graphql(`
query($owner: String!, $repo: String!, $since: DateTime!) {
repository(owner: $owner, name: $repo) {
discussions(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {
nodes {
title
createdAt
category {
name
slug
}
comments(first: 1) {
totalCount
}
}
}
}
}
`, {
owner: context.repo.owner,
repo: context.repo.repo,
since: since
});
// Get issues created in the last week
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
since: since,
state: 'all'
});
// Filter discussions created in the last week
const recentDiscussions = discussions.repository.discussions.nodes.filter(
d => new Date(d.createdAt) >= oneWeekAgo
);
// Generate and log simple report
console.log("=== Discussion-First Workflow Report ===");
console.log(`Period: ${oneWeekAgo.toDateString()} - ${new Date().toDateString()}`);
console.log(`Discussions Created: ${recentDiscussions.length}`);
console.log(`Issues Created: ${issues.data.length}`);
console.log(`Conversion Ratio: ${issues.data.length}/${recentDiscussions.length}`);
if (recentDiscussions.length > 0) {
console.log("\nDiscussion Categories:");
recentDiscussions.forEach(d => {
console.log(`- ${d.category.name}: ${d.title} (${d.comments.totalCount} comments)`);
});
} else {
console.log("No discussions this week");
}
if (issues.data.length > 0) {
console.log("\nDirect Issues Created:");
issues.data.forEach(i => {
console.log(`- ${i.title} (${new Date(i.created_at).toDateString()})`);
});
console.log("\nWARNING: Direct issues were created - check workflow configuration");
} else {
console.log("\nNo direct issues created - workflow working correctly");
}
const responseRate = recentDiscussions.length > 0 ?
(recentDiscussions.filter(d => d.comments.totalCount > 0).length / recentDiscussions.length * 100).toFixed(1) : 0;
console.log(`Community Response Rate: ${responseRate}%`);
// You could also create an issue or discussion with this report
// For now, just log it to Actions output
================================================
FILE: .gitignore
================================================
security-crawl-maze/
cmd/katana/katana
katana
*.exe
katana_*/
katana_*/
dist/
.vscode
.devcontainer
.DS_Store
.idea
================================================
FILE: .goreleaser/linux.yml
================================================
version: 2
env:
- GO111MODULE=on
before:
hooks:
- go mod tidy
project_name: katana
builds:
- id: katana-linux-amd64-generic
ldflags:
- -s -w
binary: katana
env:
- CGO_ENABLED=1
main: ./cmd/katana/main.go
goos:
- linux
goarch:
- amd64
- id: katana-linux-i386-generic
ldflags:
- -s -w
binary: katana
main: ./cmd/katana/main.go
goos:
- linux
goarch:
- 386
- id: katana-linux-arm
ldflags:
- -s -w
binary: katana
env:
- CGO_ENABLED=1
- CC=aarch64-linux-gnu-gcc
main: ./cmd/katana/main.go
goos:
- linux
goarch:
- arm64
archives:
- formats: zip
checksum:
name_template: "{{ .ProjectName }}-linux-checksums.txt"
announce:
slack:
enabled: true
channel: '#release'
username: GoReleaser
message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}'
discord:
enabled: true
message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}'
================================================
FILE: .goreleaser/mac.yml
================================================
version: 2
env:
- GO111MODULE=on
before:
hooks:
- go mod tidy
project_name: katana
builds:
- id: katana-darwin
ldflags:
- -s -w
binary: katana
env:
- CGO_ENABLED=1
main: ./cmd/katana/main.go
goos:
- darwin
goarch:
- amd64
- arm64
- 386
- arm
archives:
- formats: zip
name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}'
checksum:
name_template: "{{ .ProjectName }}-mac-checksums.txt"
================================================
FILE: .goreleaser/windows.yml
================================================
version: 2
env:
- GO111MODULE=on
before:
hooks:
- go mod tidy
project_name: katana
builds:
- id: katana-windows
ldflags:
- -s -w
binary: katana
env:
- CGO_ENABLED=1
main: ./cmd/katana/main.go
goos:
- windows
goarch:
- 386
- arm64
- amd64
archives:
- formats: zip
checksum:
name_template: "{{ .ProjectName }}-windows-checksums.txt"
================================================
FILE: Dockerfile
================================================
FROM golang:1.25.7-alpine AS build-env
RUN apk add --no-cache git gcc musl-dev
WORKDIR /app
COPY . /app
RUN go mod download
RUN go build ./cmd/katana
FROM alpine:3.23.2
RUN apk add --no-cache bind-tools ca-certificates chromium
COPY --from=build-env /app/katana /usr/local/bin/
ENTRYPOINT ["katana"]
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2022 ProjectDiscovery
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
# Go parameters
GOCMD=go
GOBUILD=$(GOCMD) build
GOMOD=$(GOCMD) mod
GOTEST=$(GOCMD) test
GOFLAGS := -v
# This should be disabled if the binary uses pprof
LDFLAGS := -s -w
ifneq ($(shell go env GOOS),darwin)
LDFLAGS := -extldflags "-static"
endif
all: build
build:
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "katana" cmd/katana/main.go
test:
$(GOTEST) $(GOFLAGS) ./...
integration:
cd integration_tests; bash run.sh cd ..
tidy:
$(GOMOD) tidy
lint:
golangci-lint run ./...
================================================
FILE: README.md
================================================
A next-generation crawling and spidering framework
Features •
Installation •
Usage •
Scope •
Config •
Filters •
Join Discord
# Features

- Fast And fully configurable web crawling
- **Standard** and **Headless** mode
- **JavaScript** parsing / crawling
- Customizable **automatic form filling**
- **Scope control** - Preconfigured field / Regex
- **Customizable output** - Preconfigured fields
- INPUT - **STDIN**, **URL** and **LIST**
- OUTPUT - **STDOUT**, **FILE** and **JSON**
## Installation
katana requires Go 1.25+ to install successfully. If you encounter any installation issues, we recommend trying with the latest available version of Go, as the minimum required version may have changed. Run the command below or download a pre-compiled binary from the [release page](https://github.com/projectdiscovery/katana/releases).
```console
CGO_ENABLED=1 go install github.com/projectdiscovery/katana/cmd/katana@latest
```
**More options to install / run katana-**
Docker
> To install / update docker to latest tag -
```sh
docker pull projectdiscovery/katana:latest
```
> To run katana in standard mode using docker -
```sh
docker run projectdiscovery/katana:latest -u https://tesla.com
```
> To run katana in headless mode using docker -
```sh
docker run projectdiscovery/katana:latest -u https://tesla.com -system-chrome -headless
```
Ubuntu
> It's recommended to install the following prerequisites -
```sh
sudo apt update
sudo snap refresh
sudo apt install zip curl wget git
sudo snap install golang --classic
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
sudo apt update
sudo apt install google-chrome-stable
```
> install katana -
```sh
go install github.com/projectdiscovery/katana/cmd/katana@latest
```
## Usage
```console
katana -h
```
This will display help for the tool. Here are all the switches it supports.
```console
Katana is a fast crawler focused on execution in automation
pipelines offering both headless and non-headless crawling.
Usage:
./katana [flags]
Flags:
INPUT:
-u, -list string[] target url / list to crawl
-resume string resume scan using resume.cfg
-e, -exclude string[] exclude host matching specified filter ('cdn', 'private-ips', cidr, ip, regex)
CONFIGURATION:
-r, -resolvers string[] list of custom resolver (file or comma separated)
-d, -depth int maximum depth to crawl (default 3)
-jc, -js-crawl enable endpoint parsing / crawling in javascript file
-jsl, -jsluice enable jsluice parsing in javascript file (memory intensive)
-ct, -crawl-duration value maximum duration to crawl the target for (s, m, h, d) (default s)
-kf, -known-files string enable crawling of known files (all,robotstxt,sitemapxml), a minimum depth of 3 is required to ensure all known files are properly crawled.
-mrs, -max-response-size int maximum response size to read (default 4194304)
-timeout int time to wait for request in seconds (default 10)
-aff, -automatic-form-fill enable automatic form filling (experimental)
-fx, -form-extraction extract form, input, textarea & select elements in jsonl output
-retry int number of times to retry the request (default 1)
-proxy string http/socks5 proxy to use
-td, -tech-detect enable technology detection
-H, -headers string[] custom header/cookie to include in all http request in header:value format (file)
-config string path to the katana configuration file
-fc, -form-config string path to custom form configuration file
-flc, -field-config string path to custom field configuration file
-s, -strategy string Visit strategy (depth-first, breadth-first) (default "depth-first")
-iqp, -ignore-query-params Ignore crawling same path with different query-param values
-fsu, -filter-similar filter crawling of similar looking URLs (e.g., /users/123 and /users/456)
-fst, -filter-similar-threshold int number of distinct values before a path position is treated as parameter (default 10)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-dr, -disable-redirects disable following redirects (default false)
-kb, -knowledge-base enable knowledge base classification
DEBUG:
-health-check, -hc run diagnostic check up
-elog, -error-log string file to write sent requests error log
-pprof-server enable pprof server
HEADLESS:
-hl, -headless enable headless hybrid crawling (experimental)
-sc, -system-chrome use local installed chrome browser instead of katana installed
-sb, -show-browser show the browser on the screen with headless mode
-ho, -headless-options string[] start headless chrome with additional options
-nos, -no-sandbox start headless chrome in --no-sandbox mode
-cdd, -chrome-data-dir string path to store chrome browser data
-scp, -system-chrome-path string use specified chrome browser for headless crawling
-noi, -no-incognito start headless chrome without incognito mode
-cwu, -chrome-ws-url string use chrome browser instance launched elsewhere with the debugger listening at this URL
-xhr, -xhr-extraction extract xhr request url,method in jsonl output
-csp, -captcha-solver-provider string captcha solver provider (e.g. capsolver)
-csk, -captcha-solver-key string captcha solver provider api key
SCOPE:
-cs, -crawl-scope string[] in scope url regex to be followed by crawler
-cos, -crawl-out-scope string[] out of scope url regex to be excluded by crawler
-fs, -field-scope string pre-defined scope field (dn,rdn,fqdn) or custom regex (e.g., '(company-staging.io|company.com)') (default "rdn")
-ns, -no-scope disables host based default scope
-do, -display-out-scope display external endpoint from scoped crawling
FILTER:
-mr, -match-regex string[] regex or list of regex to match on output url (cli, file)
-fr, -filter-regex string[] regex or list of regex to filter on output url (cli, file)
-f, -field string field to display in output (url,path,fqdn,rdn,rurl,qurl,qpath,file,ufile,key,value,kv,dir,udir) (Deprecated: use -output-template instead)
-sf, -store-field string field to store in per-host output (url,path,fqdn,rdn,rurl,qurl,qpath,file,ufile,key,value,kv,dir,udir)
-em, -extension-match string[] match output for given extension (eg, -em php,html,js)
-ef, -extension-filter string[] filter output for given extension (eg, -ef png,css)
-ndef, -no-default-ext-filter bool remove default extensions from the filter list
-mdc, -match-condition string match response with dsl based condition
-fdc, -filter-condition string filter response with dsl based condition
-duf, -disable-unique-filter disable duplicate content filtering
-fpt, -filter-page-type string[] filter response with page type (e.g. error,captcha,parked)
RATE-LIMIT:
-c, -concurrency int number of concurrent fetchers to use (default 10)
-p, -parallelism int number of concurrent inputs to process (default 10)
-rd, -delay int request delay between each request in seconds
-rl, -rate-limit int maximum requests to send per second (default 150)
-rlm, -rate-limit-minute int maximum number of requests to send per minute
UPDATE:
-up, -update update katana to latest version
-duc, -disable-update-check disable automatic katana update check
OUTPUT:
-o, -output string file to write output to
-ot, -output-template string custom output template
-sr, -store-response store http requests/responses
-srd, -store-response-dir string store http requests/responses to custom directory
-ncb, -no-clobber do not overwrite output file
-sfd, -store-field-dir string store per-host field to custom directory
-or, -omit-raw omit raw requests/responses from jsonl output
-ob, -omit-body omit response body from jsonl output
-lof, -list-output-fields list available fields for jsonl output format
-eof, -exclude-output-fields exclude fields from jsonl output
-j, -jsonl write output in jsonl format
-nc, -no-color disable output content coloring (ANSI escape codes)
-silent display output only
-v, -verbose display verbose output
-debug display debug output
-version display project version
```
## Running Katana
### Input for katana
**katana** requires **url** or **endpoint** to crawl and accepts single or multiple inputs.
Input URL can be provided using `-u` option, and multiple values can be provided using comma-separated input, similarly **file** input is supported using `-list` option and additionally piped input (stdin) is also supported.
#### URL Input
```sh
katana -u https://tesla.com
```
#### Multiple URL Input (comma-separated)
```sh
katana -u https://tesla.com,https://google.com
```
#### List Input
```bash
$ cat url_list.txt
https://tesla.com
https://google.com
```
```
katana -list url_list.txt
```
#### STDIN (piped) Input
```sh
echo https://tesla.com | katana
```
```sh
cat domains | httpx | katana
```
Example running katana -
```console
katana -u https://youtube.com
__ __
/ /_____ _/ /____ ____ ___ _
/ '_/ _ / __/ _ / _ \/ _ /
/_/\_\\_,_/\__/\_,_/_//_/\_,_/ v0.0.1
projectdiscovery.io
[WRN] Use with caution. You are responsible for your actions.
[WRN] Developers assume no liability and are not responsible for any misuse or damage.
https://www.youtube.com/
https://www.youtube.com/about/
https://www.youtube.com/about/press/
https://www.youtube.com/about/copyright/
https://www.youtube.com/t/contact_us/
https://www.youtube.com/creators/
https://www.youtube.com/ads/
https://www.youtube.com/t/terms
https://www.youtube.com/t/privacy
https://www.youtube.com/about/policies/
https://www.youtube.com/howyoutubeworks?utm_campaign=ytgen&utm_source=ythp&utm_medium=LeftNav&utm_content=txt&u=https%3A%2F%2Fwww.youtube.com%2Fhowyoutubeworks%3Futm_source%3Dythp%26utm_medium%3DLeftNav%26utm_campaign%3Dytgen
https://www.youtube.com/new
https://m.youtube.com/
https://www.youtube.com/s/desktop/4965577f/jsbin/desktop_polymer.vflset/desktop_polymer.js
https://www.youtube.com/s/desktop/4965577f/cssbin/www-main-desktop-home-page-skeleton.css
https://www.youtube.com/s/desktop/4965577f/cssbin/www-onepick.css
https://www.youtube.com/s/_/ytmainappweb/_/ss/k=ytmainappweb.kevlar_base.0Zo5FUcPkCg.L.B1.O/am=gAE/d=0/rs=AGKMywG5nh5Qp-BGPbOaI1evhF5BVGRZGA
https://www.youtube.com/opensearch?locale=en_GB
https://www.youtube.com/manifest.webmanifest
https://www.youtube.com/s/desktop/4965577f/cssbin/www-main-desktop-watch-page-skeleton.css
https://www.youtube.com/s/desktop/4965577f/jsbin/web-animations-next-lite.min.vflset/web-animations-next-lite.min.js
https://www.youtube.com/s/desktop/4965577f/jsbin/custom-elements-es5-adapter.vflset/custom-elements-es5-adapter.js
https://www.youtube.com/s/desktop/4965577f/jsbin/webcomponents-sd.vflset/webcomponents-sd.js
https://www.youtube.com/s/desktop/4965577f/jsbin/intersection-observer.min.vflset/intersection-observer.min.js
https://www.youtube.com/s/desktop/4965577f/jsbin/scheduler.vflset/scheduler.js
https://www.youtube.com/s/desktop/4965577f/jsbin/www-i18n-constants-en_GB.vflset/www-i18n-constants.js
https://www.youtube.com/s/desktop/4965577f/jsbin/www-tampering.vflset/www-tampering.js
https://www.youtube.com/s/desktop/4965577f/jsbin/spf.vflset/spf.js
https://www.youtube.com/s/desktop/4965577f/jsbin/network.vflset/network.js
https://www.youtube.com/howyoutubeworks/
https://www.youtube.com/trends/
https://www.youtube.com/jobs/
https://www.youtube.com/kids/
```
## Crawling Mode
### Standard Mode
Standard crawling modality uses the standard go http library under the hood to handle HTTP requests/responses. This modality is much faster as it doesn't have the browser overhead. Still, it analyzes HTTP responses body as is, without any javascript or DOM rendering, potentially missing post-dom-rendered endpoints or asynchronous endpoint calls that might happen in complex web applications depending, for example, on browser-specific events.
### Headless Mode
Headless mode hooks internal headless calls to handle HTTP requests/responses directly within the browser context. This offers two advantages:
- The HTTP fingerprint (TLS and user agent) fully identify the client as a legitimate browser
- Better coverage since the endpoints are discovered analyzing the standard raw response, as in the previous modality, and also the browser-rendered one with javascript enabled.
Headless crawling is optional and can be enabled using `-headless` option.
Here are other headless CLI options -
```console
katana -h headless
Flags:
HEADLESS:
-hl, -headless enable headless hybrid crawling (experimental)
-sc, -system-chrome use local installed chrome browser instead of katana installed
-sb, -show-browser show the browser on the screen with headless mode
-ho, -headless-options string[] start headless chrome with additional options
-nos, -no-sandbox start headless chrome in --no-sandbox mode
-cdd, -chrome-data-dir string path to store chrome browser data
-scp, -system-chrome-path string use specified chrome browser for headless crawling
-noi, -no-incognito start headless chrome without incognito mode
-cwu, -chrome-ws-url string use chrome browser instance launched elsewhere with the debugger listening at this URL
-xhr, -xhr-extraction extract xhr requests
-csp, -captcha-solver-provider string captcha solver provider (e.g. capsolver)
-csk, -captcha-solver-key string captcha solver provider api key
```
*`-no-sandbox`*
----
Runs headless chrome browser with **no-sandbox** option, useful when running as root user.
```console
katana -u https://tesla.com -headless -no-sandbox
```
*`-no-incognito`*
----
Runs headless chrome browser without incognito mode, useful when using the local browser.
```console
katana -u https://tesla.com -headless -no-incognito
```
*`-headless-options`*
----
When crawling in headless mode, additional chrome options can be specified using `-headless-options`, for example -
```console
katana -u https://tesla.com -headless -system-chrome -headless-options --disable-gpu,proxy-server=http://127.0.0.1:8080
```
### Captcha Solving
Katana supports automatic captcha detection and solving during headless crawling. When a captcha page is encountered, katana identifies the captcha provider, solves it via an external service, and continues crawling.
Supported captcha types: **reCAPTCHA v2**, **reCAPTCHA v3**, **reCAPTCHA Enterprise**, **Cloudflare Turnstile**, **hCaptcha**
*`-captcha-solver-provider`*
----
Option to specify the captcha solver provider. Currently supported: `capsolver`.
*`-captcha-solver-key`*
----
API key for the captcha solver provider.
```console
katana -u https://example.com -headless -csp capsolver -csk YOUR_API_KEY
```
The provider and key can also be set via environment variables:
```console
export CAPTCHA_SOLVER_PROVIDER=capsolver
export CAPTCHA_SOLVER_KEY=YOUR_API_KEY
katana -u https://example.com -headless
```
## Scope Control
Crawling can be endless if not scoped, as such katana comes with multiple support to define the crawl scope.
*`-field-scope`*
----
Most handy option to define scope with predefined field name, `rdn` being default option for field scope.
- `rdn` - crawling scoped to root domain name and all subdomains (e.g. `*example.com`) (default)
- `fqdn` - crawling scoped to given sub(domain) (e.g. `www.example.com` or `api.example.com`)
- `dn` - crawling scoped to domain name keyword (e.g. `example`)
```console
katana -u https://tesla.com -fs dn
```
*`-crawl-scope`*
------
For advanced scope control, `-cs` option can be used that comes with **regex** support.
```console
katana -u https://tesla.com -cs login
```
For multiple in scope rules, file input with multiline string / regex can be passed.
```bash
$ cat in_scope.txt
login/
admin/
app/
wordpress/
```
```console
katana -u https://tesla.com -cs in_scope.txt
```
*`-crawl-out-scope`*
-----
For defining what not to crawl, `-cos` option can be used and also support **regex** input.
```console
katana -u https://tesla.com -cos logout
```
For multiple out of scope rules, file input with multiline string / regex can be passed.
```bash
$ cat out_of_scope.txt
/logout
/log_out
```
```console
katana -u https://tesla.com -cos out_of_scope.txt
```
*`-no-scope`*
----
Katana is default to scope `*.domain`, to disable this `-ns` option can be used and also to crawl the internet.
```console
katana -u https://tesla.com -ns
```
*`-display-out-scope`*
----
As default, when scope option is used, it also applies for the links to display as output, as such **external URLs are default to exclude** and to overwrite this behavior, `-do` option can be used to display all the external URLs that exist in targets scoped URL / Endpoint.
```
katana -u https://tesla.com -do
```
Here is all the CLI options for the scope control -
```console
katana -h scope
Flags:
SCOPE:
-cs, -crawl-scope string[] in scope url regex to be followed by crawler
-cos, -crawl-out-scope string[] out of scope url regex to be excluded by crawler
-fs, -field-scope string pre-defined scope field (dn,rdn,fqdn) (default "rdn")
-ns, -no-scope disables host based default scope
-do, -display-out-scope display external endpoint from scoped crawling
```
## Crawler Configuration
Katana comes with multiple options to configure and control the crawl as the way we want.
*`-depth`*
----
Option to define the `depth` to follow the urls for crawling, the more depth the more number of endpoint being crawled + time for crawl.
```
katana -u https://tesla.com -d 5
```
*`-js-crawl`*
----
Option to enable JavaScript file parsing + crawling the endpoints discovered in JavaScript files, disabled as default.
```
katana -u https://tesla.com -jc
```
*`-crawl-duration`*
----
Option to predefined crawl duration, disabled as default.
```
katana -u https://tesla.com -ct 2
```
*`-known-files`*
----
Option to enable crawling `robots.txt` and `sitemap.xml` file, disabled as default.
```
katana -u https://tesla.com -kf robotstxt,sitemapxml
```
*`-automatic-form-fill`*
----
Option to enable automatic form filling for known / unknown fields, known field values can be customized as needed by updating form config file at `$HOME/.config/katana/form-config.yaml`.
Automatic form filling is experimental feature.
```
katana -u https://tesla.com -aff
```
*`-filter-similar`*
----
Option to filter crawling of similar looking URLs by normalizing variable path segments. This detects IDs, UUIDs, hashes, dates, and other dynamic values, and also learns repeating patterns at runtime. For example, `/users/123` and `/users/456` are treated as the same endpoint.
```
katana -u https://tesla.com -fsu
```
The promotion threshold (how many distinct values at a path position before it's treated as a parameter) can be tuned with `-fst`. Lower values are more aggressive (fewer URLs crawled), higher values are more permissive. Default is `10`.
```
katana -u https://tesla.com -fsu -fst 5
```
## Authenticated Crawling
Authenticated crawling involves including custom headers or cookies in HTTP requests to access protected resources. These headers provide authentication or authorization information, allowing you to crawl authenticated content / endpoint. You can specify headers directly in the command line or provide them as a file with katana to perform authenticated crawling.
> **Note**: User needs to be manually perform the authentication and export the session cookie / header to file to use with katana.
*`-headers`*
----
Option to add a custom header or cookie to the request.
> Syntax of [headers](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2) in the HTTP specification
Here is an example of adding a cookie to the request:
```
katana -u https://tesla.com -H 'Cookie: usrsess=AmljNrESo'
```
It is also possible to supply headers or cookies as a file. For example:
```
$ cat cookie.txt
Cookie: PHPSESSIONID=XXXXXXXXX
X-API-KEY: XXXXX
TOKEN=XX
```
```
katana -u https://tesla.com -H cookie.txt
```
There are more options to configure when needed, here is all the config related CLI options -
```console
katana -h config
Flags:
CONFIGURATION:
-r, -resolvers string[] list of custom resolver (file or comma separated)
-d, -depth int maximum depth to crawl (default 3)
-jc, -js-crawl enable endpoint parsing / crawling in javascript file
-ct, -crawl-duration int maximum duration to crawl the target for
-kf, -known-files string enable crawling of known files (all,robotstxt,sitemapxml)
-mrs, -max-response-size int maximum response size to read (default 9223372036854775807)
-timeout int time to wait for request in seconds (default 10)
-aff, -automatic-form-fill enable automatic form filling (experimental)
-fx, -form-extraction enable extraction of form, input, textarea & select elements
-retry int number of times to retry the request (default 1)
-proxy string http/socks5 proxy to use
-H, -headers string[] custom header/cookie to include in request
-config string path to the katana configuration file
-fc, -form-config string path to custom form configuration file
-flc, -field-config string path to custom field configuration file
-s, -strategy string Visit strategy (depth-first, breadth-first) (default "depth-first")
-iqp, -ignore-query-params Ignore crawling same path with different query-param values
-fsu, -filter-similar filter crawling of similar looking URLs (e.g., /users/123 and /users/456)
-fst, -filter-similar-threshold int number of distinct values before a path position is treated as parameter (default 10)
```
### Connecting to Active Browser Session
Katana can also connect to active browser session where user is already logged in and authenticated. and use it for crawling. The only requirement for this is to start browser with remote debugging enabled.
Here is an example of starting chrome browser with remote debugging enabled and using it with katana -
**step 1) First Locate path of chrome executable**
| Operating System | Chromium Executable Location | Google Chrome Executable Location |
|------------------|------------------------------|-----------------------------------|
| Windows (64-bit) | `C:\Program Files (x86)\Google\Chromium\Application\chrome.exe` | `C:\Program Files (x86)\Google\Chrome\Application\chrome.exe` |
| Windows (32-bit) | `C:\Program Files\Google\Chromium\Application\chrome.exe` | `C:\Program Files\Google\Chrome\Application\chrome.exe` |
| macOS | `/Applications/Chromium.app/Contents/MacOS/Chromium` | `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` |
| Linux | `/usr/bin/chromium` | `/usr/bin/google-chrome` |
**step 2) Start chrome with remote debugging enabled and it will return websocker url. For example, on MacOS, you can start chrome with remote debugging enabled using following command** -
```console
$ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
DevTools listening on ws://127.0.0.1:9222/devtools/browser/c5316c9c-19d6-42dc-847a-41d1aeebf7d6
```
> Now login to the website you want to crawl and keep the browser open.
**step 3) Now use the websocket url with katana to connect to the active browser session and crawl the website**
```console
katana -headless -u https://tesla.com -cwu ws://127.0.0.1:9222/devtools/browser/c5316c9c-19d6-42dc-847a-41d1aeebf7d6 -no-incognito
```
> **Note**: you can use `-cdd` option to specify custom chrome data directory to store browser data and cookies but that does not save session data if cookie is set to `Session` only or expires after certain time.
## Filters
*`-field`*
----
> [!WARNING]
> Deprecated: use [**`-output-template`**](#-output-template) instead. The field flag is still supported for backward compatibility.
Katana comes with built in fields that can be used to filter the output for the desired information, `-f` option can be used to specify any of the available fields.
```
-f, -field string field to display in output (url,path,fqdn,rdn,rurl,qurl,qpath,file,key,value,kv,dir,udir)
```
Here is a table with examples of each field and expected output when used -
| FIELD | DESCRIPTION | EXAMPLE |
| ------- | --------------------------- | ------------------------------------------------------------ |
| `url` | URL Endpoint | `https://admin.projectdiscovery.io/admin/login?user=admin&password=admin` |
| `qurl` | URL including query param | `https://admin.projectdiscovery.io/admin/login.php?user=admin&password=admin` |
| `qpath` | Path including query param | `/login?user=admin&password=admin` |
| `path` | URL Path | `https://admin.projectdiscovery.io/admin/login` |
| `fqdn` | Fully Qualified Domain name | `admin.projectdiscovery.io` |
| `rdn` | Root Domain name | `projectdiscovery.io` |
| `rurl` | Root URL | `https://admin.projectdiscovery.io` |
| `ufile` | URL with File | `https://admin.projectdiscovery.io/login.js` |
| `file` | Filename in URL | `login.php` |
| `key` | Parameter keys in URL | `user,password` |
| `value` | Parameter values in URL | `admin,admin` |
| `kv` | Keys=Values in URL | `user=admin&password=admin` |
| `dir` | URL Directory name | `/admin/` |
| `udir` | URL with Directory | `https://admin.projectdiscovery.io/admin/` |
Here is an example of using field option to only display all the urls with query parameter in it -
```
katana -u https://tesla.com -f qurl -silent
https://shop.tesla.com/en_au?redirect=no
https://shop.tesla.com/en_nz?redirect=no
https://shop.tesla.com/product/men_s-raven-lightweight-zip-up-bomber-jacket?sku=1740250-00-A
https://shop.tesla.com/product/tesla-shop-gift-card?sku=1767247-00-A
https://shop.tesla.com/product/men_s-chill-crew-neck-sweatshirt?sku=1740176-00-A
https://www.tesla.com/about?redirect=no
https://www.tesla.com/about/legal?redirect=no
https://www.tesla.com/findus/list?redirect=no
```
### Custom Fields
You can create custom fields to extract and store specific information from page responses using regex rules. These custom fields are defined using a YAML config file and are loaded from the default location at `$HOME/.config/katana/field-config.yaml`. Alternatively, you can use the `-flc` option to load a custom field config file from a different location.
Here is example custom field.
```yaml
- name: email
type: regex
regex:
- '([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)'
- '([a-zA-Z0-9+._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)'
- name: phone
type: regex
regex:
- '\d{3}-\d{8}|\d{4}-\d{7}'
```
When defining custom fields, following attributes are supported:
- **name** (required)
> The value of **name** attribute is used as the `-field` cli option value.
- **type** (required)
> The type of custom attribute, currently supported option - `regex`
- **part** (optional)
> The part of the response to extract the information from. The default value is `response`, which includes both the header and body. Other possible values are `header` and `body`.
- group (optional)
> You can use this attribute to select a specific matched group in regex, for example: `group: 1`
#### Running katana using custom field:
```console
katana -u https://tesla.com -f email,phone
```
*`-store-field`*
---
To compliment `field` option which is useful to filter output at run time, there is `-sf, -store-fields` option which works exactly like field option except instead of filtering, it stores all the information on the disk under `katana_field` directory sorted by target url. Use `-sfd` or `-store-field-dir` to store data in a different location.
```
katana -u https://tesla.com -sf key,fqdn,qurl -silent
```
```bash
$ ls katana_field/
https_www.tesla.com_fqdn.txt
https_www.tesla.com_key.txt
https_www.tesla.com_qurl.txt
```
The `-store-field` option can be useful for collecting information to build a targeted wordlist for various purposes, including but not limited to:
- Identifying the most commonly used parameters
- Discovering frequently used paths
- Finding commonly used files
- Identifying related or unknown subdomains
### Katana Filters
*`-extension-match`*
---
Crawl output can be easily matched for specific extension using `-em` option to ensure to display only output containing given extension.
```
katana -u https://tesla.com -silent -em js,jsp,json
```
*`-extension-filter`*
---
Crawl output can be easily filtered for specific extension using `-ef` option which ensure to remove all the urls containing given extension.
```
katana -u https://tesla.com -silent -ef css,txt,md
```
*`-no-default-ext-filter`*
---
Katana filters several extensions by default. This can be disabled with the `-ndef` option.
```
katana -u https://tesla.com -silent -ndef
```
*`-match-regex`*
---
The `-match-regex` or `-mr` flag allows you to filter output URLs using regular expressions. When using this flag, only URLs that match the specified regular expression will be printed in the output.
```
katana -u https://tesla.com -mr 'https://shop\.tesla\.com/*' -silent
```
*`-filter-regex`*
---
The `-filter-regex` or `-fr` flag allows you to filter output URLs using regular expressions. When using this flag, it will skip the URLs that are match the specified regular expression.
```
katana -u https://tesla.com -fr 'https://www\.tesla\.com/*' -silent
```
### Advance Filtering
Katana supports DSL-based expressions for advanced matching and filtering capabilities:
- To match endpoints with a 200 status code:
```shell
katana -u https://www.hackerone.com -mdc 'status_code == 200'
```
- To match endpoints that contain "default" and have a status code other than 403:
```shell
katana -u https://www.hackerone.com -mdc 'contains(endpoint, "default") && status_code != 403'
```
- To match endpoints with PHP technologies:
```shell
katana -u https://www.hackerone.com -mdc 'contains(to_lower(technologies), "php")'
```
- To filter out endpoints running on Cloudflare:
```shell
katana -u https://www.hackerone.com -fdc 'contains(to_lower(technologies), "cloudflare")'
```
DSL functions can be applied to any keys in the jsonl output. For more information on available DSL functions, please visit the [dsl project](https://github.com/projectdiscovery/dsl).
Here are additional filter options -
```console
katana -h filter
Flags:
FILTER:
-mr, -match-regex string[] regex or list of regex to match on output url (cli, file)
-fr, -filter-regex string[] regex or list of regex to filter on output url (cli, file)
-f, -field string field to display in output (url,path,fqdn,rdn,rurl,qurl,qpath,file,ufile,key,value,kv,dir,udir)
-sf, -store-field string field to store in per-host output (url,path,fqdn,rdn,rurl,qurl,qpath,file,ufile,key,value,kv,dir,udir)
-em, -extension-match string[] match output for given extension (eg, -em php,html,js)
-ef, -extension-filter string[] filter output for given extension (eg, -ef png,css)
-ndef, -no-default-ext-filter bool remove default extensions from the filter list
-mdc, -match-condition string match response with dsl based condition
-fdc, -filter-condition string filter response with dsl based condition
-duf, -disable-unique-filter disable duplicate content filtering
```
## Rate Limit
It's easy to get blocked / banned while crawling if not following target websites limits, katana comes with multiple option to tune the crawl to go as fast / slow we want.
*`-delay`*
-----
option to introduce a delay in seconds between each new request katana makes while crawling, disabled as default.
```
katana -u https://tesla.com -delay 20
```
*`-concurrency`*
-----
option to control the number of urls per target to fetch at the same time.
```
katana -u https://tesla.com -c 20
```
*`-parallelism`*
-----
option to define number of target to process at same time from list input.
```
katana -u https://tesla.com -p 20
```
*`-rate-limit`*
-----
option to use to define max number of request can go out per second.
```
katana -u https://tesla.com -rl 100
```
*`-rate-limit-minute`*
-----
option to use to define max number of request can go out per minute.
```
katana -u https://tesla.com -rlm 500
```
Here is all long / short CLI options for rate limit control -
```console
katana -h rate-limit
Flags:
RATE-LIMIT:
-c, -concurrency int number of concurrent fetchers to use (default 10)
-p, -parallelism int number of concurrent inputs to process (default 10)
-rd, -delay int request delay between each request in seconds
-rl, -rate-limit int maximum requests to send per second (default 150)
-rlm, -rate-limit-minute int maximum number of requests to send per minute
```
## Output
Katana support both file output in plain text format as well as JSON which includes additional information like, `source`, `tag`, and `attribute` name to co-related the discovered endpoint.
*`-output`*
---
By default, katana outputs the crawled endpoints in plain text format. The results can be written to a file by using the -output option.
```console
katana -u https://example.com -no-scope -output example_endpoints.txt
```
*`-output-template`*
---
The `-output-template` option allows you to customize the output format using template, providing flexibility in defining the output structure. This option replaces the deprecated `-field` flag for filtering output. Instead of relying on predefined fields, you can specify a custom template directly in the command line to control how the extracted data is presented.
Example of using the `-output-template` option:
```sh
katana -u https://example.com -output-template '{{email}} - {{url}}'
```
In this example, `email` represents a [custom field](#custom-fields) that extracts and displays email addresses found within the source `url`.
> [!NOTE]
> If a specified field does not exist or does not contain a value, it will simply be omitted from the output.
This option can effectively structure the output in a way that best suits your use case, making data extraction more intuitive and customizable.
*`-jsonl`*
---
```console
katana -u https://example.com -jsonl | jq .
```
```json
{
"timestamp": "2023-03-20T16:23:58.027559+05:30",
"request": {
"method": "GET",
"endpoint": "https://example.com",
"raw": "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36\r\nAccept-Encoding: gzip\r\n\r\n"
},
"response": {
"status_code": 200,
"headers": {
"accept_ranges": "bytes",
"expires": "Mon, 27 Mar 2023 10:53:58 GMT",
"last_modified": "Thu, 17 Oct 2019 07:18:26 GMT",
"content_type": "text/html; charset=UTF-8",
"server": "ECS (dcb/7EA3)",
"vary": "Accept-Encoding",
"etag": "\"3147526947\"",
"cache_control": "max-age=604800",
"x_cache": "HIT",
"date": "Mon, 20 Mar 2023 10:53:58 GMT",
"age": "331239"
},
"body": "\n\n\n Example Domain \n\n \n \n \n \n\n\n\n\n
Example Domain \n
This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.
\n
More information...
\n
\n\n\n",
"technologies": [
"Azure",
"Amazon ECS",
"Amazon Web Services",
"Docker",
"Azure CDN"
],
"raw": "HTTP/1.1 200 OK\r\nContent-Length: 1256\r\nAccept-Ranges: bytes\r\nAge: 331239\r\nCache-Control: max-age=604800\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Mon, 20 Mar 2023 10:53:58 GMT\r\nEtag: \"3147526947\"\r\nExpires: Mon, 27 Mar 2023 10:53:58 GMT\r\nLast-Modified: Thu, 17 Oct 2019 07:18:26 GMT\r\nServer: ECS (dcb/7EA3)\r\nVary: Accept-Encoding\r\nX-Cache: HIT\r\n\r\n\n\n\n Example Domain \n\n \n \n \n \n\n\n\n\n
Example Domain \n
This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.
\n
More information...
\n
\n\n\n"
}
}
```
*`-store-response`*
----
The `-store-response` option allows for writing all crawled endpoint requests and responses to a text file. When this option is used, text files including the request and response will be written to the **katana_response** directory. If you would like to specify a custom directory, you can use the `-store-response-dir` option.
```console
katana -u https://example.com -no-scope -store-response
```
```bash
$ cat katana_response/index.txt
katana_response/example.com/327c3fda87ce286848a574982ddd0b7c7487f816.txt https://example.com (200 OK)
katana_response/www.iana.org/bfc096e6dd93b993ca8918bf4c08fdc707a70723.txt http://www.iana.org/domains/reserved (200 OK)
```
**Note:**
*`-store-response` option is not supported in `-headless` mode.*
*`-list-output-fields`*
----
The `-list-output-fields` or `-lof` flag displays all available fields that can be used in JSONL output format. This is useful for understanding what data is available when using custom output templates or when excluding specific fields.
```console
katana -lof
```
*`-exclude-output-fields`*
----
The `-exclude-output-fields` or `-eof` flag allows you to exclude specific fields from the JSONL output. This is useful for reducing output size or focusing on specific data by removing unwanted fields.
```console
katana -u https://example.com -jsonl -eof raw,body
```
Here are additional CLI options related to output -
```console
katana -h output
OUTPUT:
-o, -output string file to write output to
-sr, -store-response store http requests/responses
-srd, -store-response-dir string store http requests/responses to custom directory
-lof, -list-output-fields list available fields for jsonl output format
-eof, -exclude-output-fields exclude fields from jsonl output
-j, -json write output in JSONL(ines) format
-nc, -no-color disable output content coloring (ANSI escape codes)
-silent display output only
-v, -verbose display verbose output
-version display project version
```
## Katana as a library
`katana` can be used as a library by creating an instance of the `Option` struct and populating it with the same options that would be specified via CLI. Using the options you can create `crawlerOptions` and so standard or hybrid `crawler`.
`crawler.Crawl` method should be called to crawl the input.
```go
package main
import (
"math"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/engine/standard"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/types"
)
func main() {
options := &types.Options{
MaxDepth: 3, // Maximum depth to crawl
FieldScope: "rdn", // Crawling Scope Field
BodyReadSize: math.MaxInt, // Maximum response size to read
Timeout: 10, // Timeout is the time to wait for request in seconds
Concurrency: 10, // Concurrency is the number of concurrent crawling goroutines
Parallelism: 10, // Parallelism is the number of urls processing goroutines
Delay: 0, // Delay is the delay between each crawl requests in seconds
RateLimit: 150, // Maximum requests to send per second
Strategy: "depth-first", // Visit strategy (depth-first, breadth-first)
OnResult: func(result output.Result) { // Callback function to execute for result
gologger.Info().Msg(result.Request.URL)
},
}
crawlerOptions, err := types.NewCrawlerOptions(options)
if err != nil {
gologger.Fatal().Msg(err.Error())
}
defer crawlerOptions.Close()
crawler, err := standard.New(crawlerOptions)
if err != nil {
gologger.Fatal().Msg(err.Error())
}
defer crawler.Close()
var input = "https://www.hackerone.com"
err = crawler.Crawl(input)
if err != nil {
gologger.Warning().Msgf("Could not crawl %s: %s", input, err.Error())
}
}
```
## Reporting Issues & Feature Requests
To maintain issue tracking and improve triage efficiency:
**All reports start as [GitHub Discussions](https://github.com/projectdiscovery/katana/discussions)**
- **Bug Reports** → [Start a Q&A Discussion](https://github.com/projectdiscovery/katana/discussions/new?category=q-a)
- **Feature Requests** → [Start an Ideas Discussion](https://github.com/projectdiscovery/katana/discussions/new?category=ideas)
- **Questions** → [Start a Q&A Discussion](https://github.com/projectdiscovery/katana/discussions/new?category=q-a)
**Why Discussions First?**
- **Community can help** with quick questions and troubleshooting
- **Better triage** - confirmed bugs/features become tracked issues
- **Cleaner issue tracker** - focus on actionable items only
Maintainers will convert discussions to issues when appropriate after proper review.
--------
katana is made with ❤️ by the [projectdiscovery](https://projectdiscovery.io) team and distributed under [MIT License](LICENSE.md).
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Reporting a Vulnerability
DO NOT CREATE AN ISSUE to report a security problem. Instead, please send an email to security@projectdiscovery.io, and we will acknowledge it within 3 working days.
================================================
FILE: cmd/functional-test/main.go
================================================
package main
import (
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
"github.com/projectdiscovery/katana/internal/testutils"
)
var (
debug = os.Getenv("DEBUG") == "true"
success = aurora.Green("[✓]").String()
failed = aurora.Red("[✘]").String()
errored = false
devKatanaBinary = flag.String("dev", "", "Dev Branch Katana Binary")
)
func main() {
flag.Parse()
if err := runFunctionalTests(); err != nil {
log.Fatalf("Could not run functional tests: %s\n", err)
}
if errored {
os.Exit(1)
}
}
func runFunctionalTests() error {
for _, testcase := range testutils.TestCases {
if err := runIndividualTestCase(testcase); err != nil {
errored = true
fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, testcase.Name, err)
} else {
fmt.Printf("%s Test \"%s\" passed!\n", success, testcase.Name)
}
}
return nil
}
func runIndividualTestCase(testcase testutils.TestCase) error {
argsParts := strings.Fields(testcase.Args)
devOutput, err := testutils.RunKatanaBinaryAndGetResults(testcase.Target, *devKatanaBinary, debug, argsParts)
if err != nil {
return errors.Wrap(err, "could not run Katana dev test")
}
if testcase.CompareFunc != nil {
return testcase.CompareFunc(testcase.Target, devOutput)
}
if !testutils.CompareOutput(devOutput, testcase.Expected) {
return errors.Errorf("expected output %s, got %s", testcase.Expected, devOutput)
}
return nil
}
================================================
FILE: cmd/functional-test/run.sh
================================================
#!/bin/bash
# reading os type from arguments
CURRENT_OS=$1
if [ "${CURRENT_OS}" == "windows-latest" ];then
extension=.exe
fi
echo "::group::Building functional-test binary"
go build -o functional-test$extension
echo "::endgroup::"
echo "::group::Building katana binary from current branch"
go build -o katana_dev$extension ../katana
echo "::endgroup::"
echo 'Starting katana functional test'
./functional-test$extension -dev ./katana_dev$extension
================================================
FILE: cmd/integration-test/filters.go
================================================
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"strings"
"sync/atomic"
"github.com/projectdiscovery/katana/internal/runner"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/types"
)
var filtersTestcases = map[string]TestCase{
"match condition": &matchConditionIntegrationTest{},
"filter condition": &filterConditionIntegrationTest{},
"unique filter": &uniqueFilterIntegrationTest{},
}
type matchConditionIntegrationTest struct{}
// Execute executes a test case and returns an error if occurred
// Execute the docs at ../README.md if the code stops working for integration.
func (h *matchConditionIntegrationTest) Execute() error {
results, _ := RunKatanaAndGetResults(false,
"-u", "scanme.sh",
"-match-condition", "status_code == 200 && contains(body, 'ok')",
)
if len(results) != 1 {
return fmt.Errorf("match condition failed")
}
return nil
}
type filterConditionIntegrationTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *filterConditionIntegrationTest) Execute() error {
results, _ := RunKatanaAndGetResults(false,
"-u", "scanme.sh",
"-filter-condition", "status_code == 200 && contains(body, 'ok')",
)
if len(results) != 0 {
return fmt.Errorf("filter condition failed")
}
return nil
}
type uniqueFilterIntegrationTest struct{}
func (h *uniqueFilterIntegrationTest) Execute() error {
// Create a test server that returns 404 for all paths except root
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, `
Page 1
Page 2
Page 3
Page 4
`)
} else {
w.WriteHeader(http.StatusNotFound)
// Return identical 404 content for all missing pages
_, _ = fmt.Fprint(w, `404 - Page Not Found `)
}
}))
defer server.Close()
options := types.DefaultOptions
options.URLs = []string{server.URL}
options.MaxDepth = 2
options.Concurrency = 1
options.DisableUniqueFilter = true
var fourOhFourCount atomic.Int32
options.OnResult = func(result output.Result) {
if result.Response.StatusCode == http.StatusNotFound {
fourOhFourCount.Add(1)
}
}
katanaRunner, err := runner.New(&options)
if err != nil {
return fmt.Errorf("could not create runner: %v", err)
}
defer func() {
_ = katanaRunner.Close()
}()
if err := katanaRunner.ExecuteCrawling(); err != nil {
return fmt.Errorf("could not execute crawling: %v", err)
}
if fourOhFourCount.Load() != 4 {
return fmt.Errorf("expected 4 404 responses, got %d", fourOhFourCount.Load())
}
return nil
}
// ExtraArgs
var ExtraDebugArgs = []string{}
func RunKatanaAndGetResults(debug bool, extra ...string) ([]string, error) {
cmd := exec.Command("./katana")
extra = append(extra, ExtraDebugArgs...)
cmd.Args = append(cmd.Args, extra...)
cmd.Args = append(cmd.Args, "-duc") // disable auto updates
if debug {
cmd.Args = append(cmd.Args, "-debug")
cmd.Stderr = os.Stderr
fmt.Println(cmd.String())
} else {
cmd.Args = append(cmd.Args, "-silent")
}
data, err := cmd.Output()
if debug {
fmt.Println(string(data))
}
if len(data) < 1 && err != nil {
return nil, fmt.Errorf("%v: %v", err.Error(), string(data))
}
var parts []string
items := strings.Split(string(data), "\n")
for _, i := range items {
if i != "" {
parts = append(parts, i)
}
}
return parts, nil
}
================================================
FILE: cmd/integration-test/integration-test.go
================================================
package main
import (
"fmt"
"os"
"strings"
"github.com/logrusorgru/aurora"
)
type TestCase interface {
// Execute executes a test case and returns any errors if occurred
Execute() error
}
var (
debug = os.Getenv("DEBUG") == "true"
customTest = os.Getenv("TEST")
errored = false
success = aurora.Green("[✓]").String()
failed = aurora.Red("[✘]").String()
tests = map[string]map[string]TestCase{
"code": libraryTestcases,
"filters": filtersTestcases,
}
)
func main() {
for name, tests := range tests {
fmt.Printf("Running test cases for \"%s\"\n", aurora.Blue(name))
if customTest != "" && !strings.Contains(name, customTest) {
continue // only run tests user asked
}
for name, test := range tests {
err := test.Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, name, err)
errored = true
} else {
fmt.Printf("%s Test \"%s\" passed!\n", success, name)
}
}
}
if errored {
os.Exit(1)
}
}
================================================
FILE: cmd/integration-test/library.go
================================================
package main
import (
"fmt"
"math"
"github.com/projectdiscovery/katana/pkg/engine/standard"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/types"
"github.com/projectdiscovery/katana/pkg/utils/queue"
)
var libraryTestcases = map[string]TestCase{
"katana as library": &goIntegrationTest{},
}
type goIntegrationTest struct{}
// Execute executes a test case and returns an error if occurred
// Execute the docs at ../README.md if the code stops working for integration.
func (h *goIntegrationTest) Execute() error {
var crawledURLs []string
options := &types.Options{
MaxDepth: 1,
FieldScope: "rdn",
BodyReadSize: math.MaxInt,
RateLimit: 150,
Verbose: debug,
Strategy: queue.DepthFirst.String(),
OnResult: func(r output.Result) {
crawledURLs = append(crawledURLs, r.Request.URL)
},
}
crawlerOptions, err := types.NewCrawlerOptions(options)
if err != nil {
return err
}
defer func() {
if err := crawlerOptions.Close(); err != nil {
fmt.Printf("Error closing crawler options: %v\n", err)
}
}()
crawler, err := standard.New(crawlerOptions)
if err != nil {
return err
}
defer func() {
if err := crawler.Close(); err != nil {
fmt.Printf("Error closing crawler: %v\n", err)
}
}()
var input = "https://public-firing-range.appspot.com"
err = crawler.Crawl(input)
if err != nil {
return err
}
if len(crawledURLs) == 0 {
return fmt.Errorf("no URLs crawled")
}
return nil
}
================================================
FILE: cmd/tools/crawl-maze-score/main.go
================================================
package main
import (
"bufio"
"fmt"
"log"
"math"
"os"
"strings"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/gologger"
urlutil "github.com/projectdiscovery/utils/url"
)
// expectedResults is the list of expected endpoints from security-crawl-maze
// blueprint directory.
// https://github.com/google/security-crawl-maze/blob/master/blueprints/utils/resources/expected-results.json
var expectedResults = []string{
"/css/font-face.found",
"/headers/content-location.found",
"/headers/link.found",
"/headers/location.found",
"/headers/refresh.found",
"/html/doctype.found",
"/html/manifest.found",
"/html/body/background.found",
"/html/body/a/href.found",
"/html/body/a/ping.found",
"/html/body/audio/src.found",
"/html/body/audio/source/src.found",
"/html/body/audio/source/srcset1x.found",
"/html/body/audio/source/srcset2x.found",
"/html/body/applet/archive.found",
"/html/body/applet/codebase.found",
"/html/body/blockquote/cite.found",
"/html/body/embed/src.found",
"/html/body/form/action-get.found",
"/html/body/form/action-post.found",
"/html/body/form/button/formaction.found",
"/html/body/frameset/frame/src.found",
"/html/body/iframe/src.found",
"/html/body/iframe/srcdoc.found",
"/html/body/img/dynsrc.found",
"/html/body/img/lowsrc.found",
"/html/body/img/longdesc.found",
"/html/body/img/src-data.found",
"/html/body/img/src.found",
"/html/body/img/srcset1x.found",
"/html/body/img/srcset2x.found",
"/html/body/input/src.found",
"/html/body/isindex/action.found",
"/html/body/map/area/ping.found",
"/html/body/object/data.found",
"/html/body/object/codebase.found",
"/html/body/object/param/value.found",
"/html/body/script/src.found",
"/html/body/svg/image/xlink.found",
"/html/body/svg/script/xlink.found",
"/html/body/table/background.found",
"/html/body/table/td/background.found",
"/html/body/video/src.found",
"/html/body/video/track/src.found",
"/html/body/video/poster.found",
"/html/head/profile.found",
"/html/head/base/href.found",
"/html/head/comment-conditional.found",
"/html/head/import/implementation.found",
"/html/head/link/href.found",
"/html/head/meta/content-csp.found",
"/html/head/meta/content-pinned-websites.found",
"/html/head/meta/content-reading-view.found",
"/html/head/meta/content-redirect.found",
"/html/misc/url/full-url.found",
"/html/misc/url/path-relative-url.found",
"/html/misc/url/protocol-relative-url.found",
"/html/misc/url/root-relative-url.found",
"/html/misc/string/dot-dot-slash-prefix.found",
"/html/misc/string/dot-slash-prefix.found",
"/html/misc/string/url-string.found",
"/html/misc/string/string-known-extension.pdf",
"/javascript/misc/automatic-post.found",
"/javascript/misc/comment.found",
"/javascript/misc/string-variable.found",
"/javascript/misc/string-concat-variable.found",
"/javascript/frameworks/angular/event-handler.found",
"/javascript/frameworks/angular/router-outlet.found",
"/javascript/frameworks/angularjs/ng-href.found",
"/javascript/frameworks/polymer/event-handler.found",
"/javascript/frameworks/polymer/polymer-router.found",
"/javascript/frameworks/react/route-path.found",
"/javascript/frameworks/react/index.html/search.found",
"/javascript/interactive/js-delete.found",
"/javascript/interactive/js-post.found",
"/javascript/interactive/js-post-event-listener.found",
"/javascript/interactive/js-put.found",
"/javascript/interactive/listener-and-event-attribute-first.found",
"/javascript/interactive/listener-and-event-attribute-second.found",
"/javascript/interactive/multi-step-request-event-attribute.found",
"/test/javascript/interactive/multi-step-request-event-listener-div-dom.found",
"/test/javascript/interactive/multi-step-request-event-listener-div.found",
"/javascript/interactive/multi-step-request-event-listener-dom.found",
"/javascript/interactive/multi-step-request-event-listener.found",
"/javascript/interactive/multi-step-request-redefine-event-attribute.found",
"/javascript/interactive/multi-step-request-remove-button.found",
"/javascript/interactive/multi-step-request-remove-event-listener.found",
"/javascript/interactive/two-listeners-first.found",
"/javascript/interactive/two-listeners-second.found",
"/misc/known-files/robots.txt.found",
"/misc/known-files/sitemap.xml.found",
}
func main() {
if err := process(); err != nil {
log.Fatalf("%s\n", err)
}
}
var urlTestPrefix = "/test"
func process() error {
if len(os.Args) < 3 {
fmt.Printf("Usage: crawl-maze-score output.txt output_headless.txt")
return nil
}
input := os.Args[1]
inputHeadless := os.Args[2]
links, err := readFoundLinks(input)
if err != nil {
return err
}
linksHeadless, err := readFoundLinks(inputHeadless)
if err != nil {
return err
}
linksMap := make(map[string]struct{})
linksHeadlessMap := make(map[string]struct{})
for _, link := range links {
linksMap[link] = struct{}{}
}
for _, link := range linksHeadless {
linksHeadlessMap[link] = struct{}{}
}
matches, matchesHeadless := 0, 0
for _, expected := range expectedResults {
expected = urlTestPrefix + expected
_, normalOk := linksMap[expected]
_, headlessOk := linksHeadlessMap[expected]
if normalOk {
matches++
}
if headlessOk {
matchesHeadless++
}
fmt.Printf("[%s] [%s] %s\n", colorizeText("standard", normalOk), colorizeText("headless", headlessOk), expected)
}
fmt.Printf("[info] Total links (%d): Standard=>%d Headless=>%d\n", len(expectedResults), len(links), len(linksHeadless))
fmt.Printf("[info] Total: %d NormalMatches=>%d HeadlessMatches=>%d\n", len(expectedResults), matches, matchesHeadless)
fmt.Printf("[info] Score: Normal=>%.2f%% Headless=>%.2f%%\n", math.Round(float64(matches*100/len(expectedResults))), math.Round(float64(matchesHeadless*100/len(expectedResults))))
return nil
}
func colorizeText(text string, value bool) string {
if value {
return aurora.Green(text + ":yes").String()
}
return aurora.Red(text + ":no").String()
}
func strippedLink(link string) string {
parsed, err := urlutil.Parse(link)
if err != nil {
gologger.Warning().Msgf("failed to parse link while extracting path: %v", err)
}
return parsed.Path
}
func readFoundLinks(input string) ([]string, error) {
file, err := os.Open(input)
if err != nil {
return nil, err
}
defer func() {
if err := file.Close(); err != nil {
gologger.Error().Msgf("Error closing file: %v\n", err)
}
}()
scanner := bufio.NewScanner(file)
var links []string
for scanner.Scan() {
text := scanner.Text()
if text == "" {
break
}
if strings.Contains(text, ".found") {
links = append(links, strippedLink(text))
}
}
return links, nil
}
================================================
FILE: go.mod
================================================
module github.com/projectdiscovery/katana
go 1.25.7
require (
github.com/BishopFox/jsluice v0.0.0-20240110145140-0ddfab153e06
github.com/PuerkitoBio/goquery v1.11.0
github.com/adrianbrad/queue v1.3.0
github.com/dominikbraun/graph v0.23.0
github.com/go-rod/rod v0.116.2
github.com/happyhackingspace/dit v0.0.14
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/json-iterator/go v1.1.12
github.com/lmittmann/tint v1.0.6
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3
github.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6
github.com/mitchellh/mapstructure v1.5.0
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/dsl v0.8.5
github.com/projectdiscovery/fastdialer v0.5.2
github.com/projectdiscovery/goflags v0.1.74
github.com/projectdiscovery/gologger v1.1.67
github.com/projectdiscovery/hmap v0.0.99
github.com/projectdiscovery/mapcidr v1.1.97
github.com/projectdiscovery/ratelimit v0.0.82
github.com/projectdiscovery/retryablehttp-go v1.3.2
github.com/projectdiscovery/utils v0.8.0
github.com/projectdiscovery/wappalyzergo v0.2.62
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.5.0
github.com/stoewer/go-strcase v1.3.0
github.com/stretchr/testify v1.11.1
github.com/valyala/fasttemplate v1.2.2
go.uber.org/multierr v1.11.0
golang.org/x/net v0.51.0
gopkg.in/yaml.v3 v3.0.1
)
require (
aead.dev/minisign v0.2.0 // indirect
github.com/Knetic/govaluate v3.0.0+incompatible // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/brianvoe/gofakeit/v7 v7.2.1 // indirect
github.com/charmbracelet/glamour v0.8.0 // indirect
github.com/charmbracelet/lipgloss v0.13.0 // indirect
github.com/charmbracelet/x/ansi v0.3.2 // indirect
github.com/cheggaaa/pb/v3 v3.1.4 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/gaissmai/bart v0.26.0 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gosimple/slug v1.15.0 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hdm/jarm-go v0.0.7 // indirect
github.com/iangcarroll/cookiemonster v1.6.0 // indirect
github.com/kataras/jwt v0.1.8 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/logrusorgru/aurora/v4 v4.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mholt/archives v0.1.5 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/pierrec/lz4/v4 v4.1.23 // indirect
github.com/projectdiscovery/asnmap v1.1.1 // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
github.com/projectdiscovery/gostruct v0.0.2 // indirect
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
github.com/refraction-networking/utls v1.7.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sashabaranov/go-openai v1.37.0 // indirect
github.com/shirou/gopsutil/v3 v3.23.7 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/smacker/go-tree-sitter v0.0.0-20230720070738-0d0a9f78d8f8 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/tidwall/btree v1.6.0 // indirect
github.com/tidwall/buntdb v1.3.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vulncheck-oss/go-exploit v1.51.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/yuin/goldmark v1.7.4 // indirect
github.com/yuin/goldmark-emoji v1.0.3 // indirect
github.com/zcalusic/sysinfo v1.0.2 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
)
require (
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
github.com/akrylysov/pogreb v0.10.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/projectdiscovery/networkpolicy v0.1.33
github.com/projectdiscovery/retryabledns v1.0.112 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/weppos/publicsuffix-go v0.40.3-0.20250408071509-6074bbe7fd39 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.41.0 // indirect
gopkg.in/yaml.v2 v2.4.0
)
================================================
FILE: go.sum
================================================
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BishopFox/jsluice v0.0.0-20240110145140-0ddfab153e06 h1:xa/dJgg1qpWdIyr7tQcTV2TUPgBK/f0TTMLMmD5GqjQ=
github.com/BishopFox/jsluice v0.0.0-20240110145140-0ddfab153e06/go.mod h1:ENDk4KXEVPZTZPygQAEWJK0BlyEWAyQZhxwCMc+o6A0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub47e7kd2PLZeACxc1LkiiNoDOFRClE=
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057/go.mod h1:iLB2pivrPICvLOuROKmlqURtFIEsoJZaMidQfCG1+D4=
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8=
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE=
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/adrianbrad/queue v1.3.0 h1:8FH1N+93HXbqta5+URa1AL+diV7MP3VDXAEnP+DNp48=
github.com/adrianbrad/queue v1.3.0/go.mod h1:wYiPC/3MPbyT45QHLrPR4zcqJWPePubM1oEP/xTwhUs=
github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY=
github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/brianvoe/gofakeit/v7 v7.2.1 h1:AGojgaaCdgq4Adzrd2uWdbGNDyX6MWNhHdQBraNfOHI=
github.com/brianvoe/gofakeit/v7 v7.2.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs=
github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY=
github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354=
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gaissmai/bart v0.26.0 h1:xOZ57E9hJLBiQaSyeZa9wgWhGuzfGACgqp4BE77OkO0=
github.com/gaissmai/bart v0.26.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4=
github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/happyhackingspace/dit v0.0.14 h1:rkIu0HuFqvqr8F2PJgG0F+lx6DbX/tQE1hXKwIF2NQQ=
github.com/happyhackingspace/dit v0.0.14/go.mod h1:+WeAxrX7QYeiDmXLVaDgrqpyfD4O/sHlOL4wtbiIpUQ=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A=
github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iangcarroll/cookiemonster v1.6.0 h1:NPFkn/ZZYZgzXhJ1awRnYhZ3fJK3hKWgbctfTW21kew=
github.com/iangcarroll/cookiemonster v1.6.0/go.mod h1:n3MvoAq56NkNyCEyhcYs3ZJMzTc9rL3w7IaITI0apMg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kataras/jwt v0.1.8 h1:u71baOsYD22HWeSOg32tCHbczPjdCk7V4MMeJqTtmGk=
github.com/kataras/jwt v0.1.8/go.mod h1:Q5j2IkcIHnfwy+oNY3TVWuEBJNw0ADgCcXK9CaZwV4o=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lmittmann/tint v1.0.6 h1:vkkuDAZXc0EFGNzYjWcV0h7eEX+uujH48f/ifSkJWgc=
github.com/lmittmann/tint v1.0.6/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3 h1:l1rIRmxNhzeQM+qA3D0CsDLo0Hx45q9JmK0BlCjt6Ks=
github.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3/go.mod h1:j16TYl5p17+vBMyaL6Nu4ojlOnfX8lc2k2cfmw6m5TQ=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6 h1:bjfMeqxWEJ6IRUvGkiTkSwx0a6UdQJsbirRSoXogteY=
github.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6/go.mod h1:WVJJvUw/pIOcwu2O8ZzHEhmigq2jzwRNfJVRMJB7bR8=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc=
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=
github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU=
github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kID2iwsDqI=
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
github.com/projectdiscovery/dsl v0.8.5 h1:f3opg8Jzikwx6VXC+CbgseUmSUqdfCnfGT08Syhp0sw=
github.com/projectdiscovery/dsl v0.8.5/go.mod h1:AuUq18cpLJJ0uAjJZKaLrdyAgDHrnQAjLMZtPEyMoJw=
github.com/projectdiscovery/fastdialer v0.5.2 h1:BrK23yWc0XD57DMLqnF5oM5tBy8xx9brin+zoSo6gCw=
github.com/projectdiscovery/fastdialer v0.5.2/go.mod h1:euoxS1E93LDnl0OnNN0UALedAFF+EehBxyU3z+79l0g=
github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c=
github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4=
github.com/projectdiscovery/gologger v1.1.67 h1:GZU3AjYiJvcwJT5TlfIv+152/TVmaz62Zyn3/wWXlig=
github.com/projectdiscovery/gologger v1.1.67/go.mod h1:35oeQP6wvj58S+o+Km6boED/t786FXQkI0exhFHJbNE=
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=
github.com/projectdiscovery/hmap v0.0.99 h1:XPfLnD3CUrMqVCIdpK9ozD7Xmp3simx3T+2j4WWhHnU=
github.com/projectdiscovery/hmap v0.0.99/go.mod h1:koyUJi83K5G3w35ZLFXOYZIyYJsO+6hQrgDDN1RBrVE=
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q=
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8QuWs2puwy+2w=
github.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA=
github.com/projectdiscovery/networkpolicy v0.1.33 h1:bVgp+XpLEsQ7ZEJt3UaUqIwhI01MMdt7F2dfIKFQg/w=
github.com/projectdiscovery/networkpolicy v0.1.33/go.mod h1:YAPddAXUc/lhoU85AFdvgOQKx8Qh8r0vzSjexRWk6Yk=
github.com/projectdiscovery/ratelimit v0.0.82 h1:rtO5SQf5uQFu5zTahTaTcO06OxmG8EIF1qhdFPIyTak=
github.com/projectdiscovery/ratelimit v0.0.82/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=
github.com/projectdiscovery/retryabledns v1.0.112 h1:4iCiuo6jMnw/pdOZRzBQrbUOUu5tOeuvGupxVV8RDLw=
github.com/projectdiscovery/retryabledns v1.0.112/go.mod h1:xsJTKbo+KGqd7+88z1naEUFJybLH2yjB/zUyOweA7k0=
github.com/projectdiscovery/retryablehttp-go v1.3.2 h1:Rv2gw/8t3QZz+WIuHUspVBoRrpBWpVOhzh/wLUGYSVM=
github.com/projectdiscovery/retryablehttp-go v1.3.2/go.mod h1:q1EQ+FX9JP5Z0EqLXDf+8b6XdzWmBXIMPowpI6hQ9aU=
github.com/projectdiscovery/utils v0.8.0 h1:8d79OCs5xGDNXdKxMUKMY/lgQSUWJMYB1B2Sx+oiqkQ=
github.com/projectdiscovery/utils v0.8.0/go.mod h1:CU6tjtyTRxBrnNek+GPJplw4IIHcXNZNKO09kWgqTdg=
github.com/projectdiscovery/wappalyzergo v0.2.62 h1:SMZ70bLCj6jHnFgjanuiaQpqUXY6aiEC3YoM0ZSvYes=
github.com/projectdiscovery/wappalyzergo v0.2.62/go.mod h1:8FtSVcmPRZU0g1euBpdSYEBHIvB7Zz9MOb754ZqZmfU=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/refraction-networking/utls v1.7.1 h1:dxg+jla3uocgN8HtX+ccwDr68uCBBO3qLrkZUbqkcw0=
github.com/refraction-networking/utls v1.7.1/go.mod h1:TUhh27RHMGtQvjQq+RyO11P6ZNQNBb3N0v7wsEjKAIQ=
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uRPSpfNZkY=
github.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smacker/go-tree-sitter v0.0.0-20230720070738-0d0a9f78d8f8 h1:DxgjlvWYsb80WEN2Zv3WqJFAg2DKjUQJO6URGdf1x6Y=
github.com/smacker/go-tree-sitter v0.0.0-20230720070738-0d0a9f78d8f8/go.mod h1:q99oHDsbP0xRwmn7Vmob8gbSMNyvJ83OauXPSuHQuKE=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA=
github.com/tidwall/buntdb v1.3.0/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vulncheck-oss/go-exploit v1.51.0 h1:HTmJ4Q94tbEDPb35mQZn6qMg4rT+Sw9n+L7g3Pjr+3o=
github.com/vulncheck-oss/go-exploit v1.51.0/go.mod h1:J28w0dLnA6DnCrnBm9Sbt6smX8lvztnnN2wCXy7No6c=
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db/go.mod h1:aiQaH1XpzIfgrJq3S1iw7w+3EDbRP7mF5fmwUhWyRUs=
github.com/weppos/publicsuffix-go v0.40.3-0.20250408071509-6074bbe7fd39 h1:Bz/zVM/LoGZ9IztGBHrq2zlFQQbEG8dBYnxb4hamIHM=
github.com/weppos/publicsuffix-go v0.40.3-0.20250408071509-6074bbe7fd39/go.mod h1:2oFzEwGYI7lhiqG0YkkcKa6VcpjVinQbWxaPzytDmLA=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
github.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg=
github.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=
github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4=
github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zcalusic/sysinfo v1.0.2 h1:nwTTo2a+WQ0NXwo0BGRojOJvJ/5XKvQih+2RrtWqfxc=
github.com/zcalusic/sysinfo v1.0.2/go.mod h1:kluzTYflRWo6/tXVMJPdEjShsbPpsFRyy+p1mBQPC30=
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30=
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk=
github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 h1:YOQ1vXEwE4Rnj+uQ/3oCuJk5wgVsvUyW+glsndwYuyA=
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968/go.mod h1:xIuOvYCZX21S5Z9bK1BMrertTGX/F8hgAPw7ERJRNS0=
github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
================================================
FILE: integration_tests/run.sh
================================================
#!/bin/bash
echo "::group::Build katana"
rm integration-test katana 2>/dev/null
cd ../cmd/katana
go build
mv katana ../../integration_tests/katana
echo "::endgroup::"
echo "::group::Build katana integration-test"
cd ../integration-test
go build
mv integration-test ../../integration_tests/integration-test
cd ../../integration_tests
echo "::endgroup::"
./integration-test
if [ $? -eq 0 ]
then
exit 0
else
exit 1
fi
================================================
FILE: internal/runner/banner.go
================================================
package runner
import (
"github.com/projectdiscovery/gologger"
updateutils "github.com/projectdiscovery/utils/update"
)
var banner = (`
__ __
/ /_____ _/ /____ ____ ___ _
/ '_/ _ / __/ _ / _ \/ _ /
/_/\_\\_,_/\__/\_,_/_//_/\_,_/
`)
var version = "v1.5.0"
// showBanner is used to show the banner to the user
func showBanner() {
gologger.Print().Msgf("%s\n", banner)
gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n")
}
// GetUpdateCallback returns a callback function that updates katana
func GetUpdateCallback() func() {
return func() {
showBanner()
updateutils.GetUpdateToolCallback("katana", version)()
}
}
================================================
FILE: internal/runner/executer.go
================================================
package runner
import (
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/utils/errkit"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/remeh/sizedwaitgroup"
)
// ExecuteCrawling executes the crawling main loop
func (r *Runner) ExecuteCrawling() error {
if r.crawler == nil {
return errkit.New("crawler is not initialized")
}
inputs := r.parseInputs()
if len(inputs) == 0 {
return errkit.New("no input provided for crawling")
}
for _, input := range inputs {
_ = r.state.InFlightUrls.Set(addSchemeIfNotExists(input), struct{}{})
}
defer func() {
if err := r.crawler.Close(); err != nil {
gologger.Error().Msgf("Error closing crawler: %v\n", err)
}
}()
wg := sizedwaitgroup.New(r.options.Parallelism)
for _, input := range inputs {
if !r.networkpolicy.Validate(input) {
gologger.Info().Msgf("Skipping excluded host %s", input)
continue
}
wg.Add()
input = addSchemeIfNotExists(input)
go func(input string) {
defer wg.Done()
if err := r.crawler.Crawl(input); err != nil {
gologger.Warning().Msgf("Could not crawl %s: %s", input, err)
}
r.state.InFlightUrls.Delete(input)
}(input)
}
wg.Wait()
return nil
}
// scheme less urls are skipped and are required for headless mode and other purposes
// this method adds scheme if given input does not have any
func addSchemeIfNotExists(inputURL string) string {
if strings.HasPrefix(inputURL, urlutil.HTTP) || strings.HasPrefix(inputURL, urlutil.HTTPS) {
return inputURL
}
parsed, err := urlutil.Parse(inputURL)
if err != nil {
gologger.Warning().Msgf("input %v is not a valid url got %v", inputURL, err)
return inputURL
}
if parsed.Port() != "" && (parsed.Port() == "80" || parsed.Port() == "8080") {
return urlutil.HTTP + urlutil.SchemeSeparator + inputURL
} else {
return urlutil.HTTPS + urlutil.SchemeSeparator + inputURL
}
}
================================================
FILE: internal/runner/healthcheck.go
================================================
package runner
import (
"fmt"
"net"
"os/exec"
"runtime"
"strings"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/katana/pkg/types"
fileutil "github.com/projectdiscovery/utils/file"
permissionutil "github.com/projectdiscovery/utils/permission"
)
func DoHealthCheck(options *types.Options, flagSet *goflags.FlagSet) string {
// RW permissions on config file
cfgFilePath, _ := flagSet.GetConfigFilePath()
var test strings.Builder
_, _ = fmt.Fprintf(&test, "Version: %s\n", version)
_, _ = fmt.Fprintf(&test, "Operative System: %s\n", runtime.GOOS)
_, _ = fmt.Fprintf(&test, "Architecture: %s\n", runtime.GOARCH)
_, _ = fmt.Fprintf(&test, "Go Version: %s\n", runtime.Version())
_, _ = fmt.Fprintf(&test, "Compiler: %s\n", runtime.Compiler)
var testResult string
if permissionutil.IsRoot {
testResult = "Ok"
} else {
testResult = "Ko"
}
_, _ = fmt.Fprintf(&test, "root: %s\n", testResult)
ok, err := fileutil.IsReadable(cfgFilePath)
if ok {
testResult = "Ok"
} else {
testResult = "Ko"
}
if err != nil {
testResult += fmt.Sprintf(" (%s)", err)
}
_, _ = fmt.Fprintf(&test, "Config file \"%s\" Read => %s\n", cfgFilePath, testResult)
ok, err = fileutil.IsWriteable(cfgFilePath)
if ok {
testResult = "Ok"
} else {
testResult = "Ko"
}
if err != nil {
testResult += fmt.Sprintf(" (%s)", err)
}
fmt.Fprintf(&test, "Config file \"%s\" Write => %s\n", cfgFilePath, testResult)
c4, err := net.Dial("tcp4", "scanme.sh:80")
if err == nil && c4 != nil {
_ = c4.Close()
}
testResult = "Ok"
if err != nil {
testResult = fmt.Sprintf("Ko (%s)", err)
}
fmt.Fprintf(&test, "TCP IPv4 connectivity to scanme.sh:80 => %s\n", testResult)
c6, err := net.Dial("tcp6", "scanme.sh:80")
if err == nil && c6 != nil {
_ = c6.Close()
}
testResult = "Ok"
if err != nil {
testResult = fmt.Sprintf("Ko (%s)", err)
}
_, _ = fmt.Fprintf(&test, "TCP IPv6 connectivity to scanme.sh:80 => %s\n", testResult)
u4, err := net.Dial("udp4", "scanme.sh:53")
if err == nil && u4 != nil {
_ = u4.Close()
}
testResult = "Ok"
if err != nil {
testResult = fmt.Sprintf("Ko (%s)", err)
}
_, _ = fmt.Fprintf(&test, "UDP IPv4 connectivity to scanme.sh:80 => %s\n", testResult)
u6, err := net.Dial("udp6", "scanme.sh:80")
if err == nil && u6 != nil {
_ = u6.Close()
}
testResult = "Ok"
if err != nil {
testResult = fmt.Sprintf("Ko (%s)", err)
}
_, _ = fmt.Fprintf(&test, "UDP IPv6 connectivity to scanme.sh:80 => %s\n", testResult)
// attempt to identify if chome is installed locally
if chromePath, err := exec.LookPath("chrome"); err == nil {
_, _ = fmt.Fprintf(&test, "Potential chrome binary path (linux/osx) => %s\n", chromePath)
}
if chromePath, err := exec.LookPath("chrome.exe"); err == nil {
_, _ = fmt.Fprintf(&test, "Potential chrome.exe binary path (windows) => %s\n", chromePath)
}
return test.String()
}
================================================
FILE: internal/runner/options.go
================================================
package runner
import (
"bufio"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/formatter"
"github.com/projectdiscovery/katana/pkg/types"
"github.com/projectdiscovery/katana/pkg/utils"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
"gopkg.in/yaml.v3"
)
// validateOptions validates the provided options for crawler
func validateOptions(options *types.Options) error {
if options.MaxDepth <= 0 && options.CrawlDuration.Seconds() <= 0 {
return errkit.New("either max-depth or crawl-duration must be specified")
}
if len(options.URLs) == 0 && !fileutil.HasStdin() {
return errkit.New("no inputs specified for crawler")
}
// Disabling automatic form fill (-aff) for headless navigation due to incorrect implementation.
// Form filling should be handled via headless actions within the page context
if options.HeadlessHybrid && options.AutomaticFormFill {
options.AutomaticFormFill = false
gologger.Info().Msgf("Automatic form fill (-aff) has been disabled for headless navigation.")
}
// Disallow ambiguous engine selection
if options.Headless && options.HeadlessHybrid {
return errkit.New("flags -hl (headless) and -hh (hybrid) are mutually exclusive")
}
if (options.HeadlessOptionalArguments != nil || options.HeadlessNoSandbox || options.SystemChromePath != "") &&
!options.Headless && !options.HeadlessHybrid {
return errkit.New("headless (-hl) or hybrid (-hh) mode is required if -ho, -nos or -scp are set")
}
if (options.HeadlessOptionalArguments != nil || options.HeadlessNoSandbox || options.SystemChromePath != "") && !options.Headless && !options.HeadlessHybrid {
return errkit.New("headless mode (-hl) is required if -ho, -nos or -scp are set")
}
if options.SystemChromePath != "" {
if !fileutil.FileExists(options.SystemChromePath) {
return errkit.New("specified system chrome binary does not exist")
}
}
if options.StoreResponseDir != "" && !options.StoreResponse {
gologger.Debug().Msgf("store response directory specified, enabling \"sr\" flag automatically\n")
options.StoreResponse = true
}
for _, mr := range options.OutputMatchRegex {
cr, err := regexp.Compile(mr)
if err != nil {
return errkit.Wrap(err, "Invalid value for match regex option")
}
options.MatchRegex = append(options.MatchRegex, cr)
}
for _, fr := range options.OutputFilterRegex {
cr, err := regexp.Compile(fr)
if err != nil {
return errkit.Wrap(err, "Invalid value for filter regex option")
}
options.FilterRegex = append(options.FilterRegex, cr)
}
if options.KnownFiles != "" && options.MaxDepth < 3 {
gologger.Info().Msgf("Depth automatically set to 3 to accommodate the `--known-files` option (originally set to %d).", options.MaxDepth)
options.MaxDepth = 3
}
gologger.DefaultLogger.SetFormatter(formatter.NewCLI(options.NoColors))
return nil
}
// readCustomFormConfig reads custom form fill config
func readCustomFormConfig(formConfig string) error {
file, err := os.Open(formConfig)
if err != nil {
return errkit.Wrap(err, "could not read form config")
}
defer func() {
if err := file.Close(); err != nil {
gologger.Error().Msgf("Error closing file: %v\n", err)
}
}()
var data utils.FormFillData
if err := yaml.NewDecoder(file).Decode(&data); err != nil {
return errkit.Wrap(err, "could not decode form config")
}
utils.FormData = data
return nil
}
// parseInputs parses the inputs returning a slice of URLs
func (r *Runner) parseInputs() []string {
values := make(map[string]struct{})
for _, url := range r.options.URLs {
if url == "" {
continue
}
value := normalizeInput(url)
if _, ok := values[value]; !ok {
values[value] = struct{}{}
}
}
if r.stdin {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
value := normalizeInput(scanner.Text())
if _, ok := values[value]; !ok {
values[value] = struct{}{}
}
}
}
final := make([]string, 0, len(values))
for k := range values {
final = append(final, k)
}
return final
}
func normalizeInput(value string) string {
return strings.TrimSpace(value)
}
func initExampleFormFillConfig() error {
homedir, err := os.UserHomeDir()
if err != nil {
return errkit.Wrap(err, "could not get home directory")
}
defaultConfig := filepath.Join(homedir, ".config", "katana", "form-config.yaml")
if fileutil.FileExists(defaultConfig) {
return readCustomFormConfig(defaultConfig)
}
if err := os.MkdirAll(filepath.Dir(defaultConfig), 0775); err != nil {
return err
}
exampleConfig, err := os.Create(defaultConfig)
if err != nil {
return errkit.Wrap(err, "could not get home directory")
}
defer func() {
if err := exampleConfig.Close(); err != nil {
gologger.Error().Msgf("Error closing example config: %v\n", err)
}
}()
err = yaml.NewEncoder(exampleConfig).Encode(utils.DefaultFormFillData)
return err
}
================================================
FILE: internal/runner/runner.go
================================================
package runner
import (
"encoding/json"
"os"
"strconv"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/engine"
"github.com/projectdiscovery/katana/pkg/engine/headless"
"github.com/projectdiscovery/katana/pkg/engine/hybrid"
"github.com/projectdiscovery/katana/pkg/engine/standard"
"github.com/projectdiscovery/katana/pkg/types"
"github.com/projectdiscovery/mapcidr"
"github.com/projectdiscovery/mapcidr/asn"
"github.com/projectdiscovery/networkpolicy"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
iputil "github.com/projectdiscovery/utils/ip"
mapsutil "github.com/projectdiscovery/utils/maps"
updateutils "github.com/projectdiscovery/utils/update"
"go.uber.org/multierr"
)
// Runner creates the required resources for crawling
// and executes the crawl process.
type Runner struct {
crawlerOptions *types.CrawlerOptions
stdin bool
crawler engine.Engine
options *types.Options
state *RunnerState
networkpolicy *networkpolicy.NetworkPolicy
}
type RunnerState struct {
InFlightUrls *mapsutil.SyncLockMap[string, struct{}]
}
// New returns a new crawl runner structure
func New(options *types.Options) (*Runner, error) {
// create the resume configuration structure
if options.ShouldResume() {
gologger.Info().Msg("Resuming from save checkpoint")
file, err := os.ReadFile(options.Resume)
if err != nil {
return nil, err
}
runnerState := &RunnerState{}
err = json.Unmarshal(file, runnerState)
if err != nil {
return nil, err
}
options.URLs = mapsutil.GetKeys(runnerState.InFlightUrls.GetAll())
}
options.ConfigureOutput()
showBanner()
if options.Version {
gologger.Info().Msgf("Current version: %s", version)
return nil, nil
}
if !options.DisableUpdateCheck {
latestVersion, err := updateutils.GetToolVersionCallback("katana", version)()
if err != nil {
if options.Verbose {
gologger.Error().Msgf("katana version check failed: %v", err.Error())
}
} else {
gologger.Info().Msgf("Current katana version %v %v", version, updateutils.GetVersionDescription(version, latestVersion))
}
}
if err := initExampleFormFillConfig(); err != nil {
return nil, errkit.Wrap(err, "could not init default config")
}
if err := validateOptions(options); err != nil {
return nil, errkit.Wrap(err, "could not validate options")
}
if options.FormConfig != "" {
if err := readCustomFormConfig(options.FormConfig); err != nil {
return nil, err
}
}
crawlerOptions, err := types.NewCrawlerOptions(options)
if err != nil {
return nil, errkit.Wrap(err, "could not create crawler options")
}
var crawler engine.Engine
switch {
case options.ChromeWSUrl != "":
// When connecting to existing browser via WebSocket URL,
// use hybrid engine regardless of other flags
// (ChromeWSUrl takes precedence over -headless flag)
crawler, err = hybrid.New(crawlerOptions)
case options.Headless:
crawler, err = headless.New(crawlerOptions)
case options.HeadlessHybrid:
crawler, err = hybrid.New(crawlerOptions)
default:
crawler, err = standard.New(crawlerOptions)
}
if err != nil {
return nil, errkit.Wrap(err, "could not create standard crawler")
}
var npOptions networkpolicy.Options
for _, exclude := range options.Exclude {
switch {
case exclude == "cdn":
//implement cdn check in netoworkpolicy pkg??
continue
case exclude == "private-ips":
npOptions.DenyList = append(npOptions.DenyList, networkpolicy.DefaultIPv4Denylist...)
npOptions.DenyList = append(npOptions.DenyList, networkpolicy.DefaultIPv4DenylistRanges...)
npOptions.DenyList = append(npOptions.DenyList, networkpolicy.DefaultIPv6Denylist...)
npOptions.DenyList = append(npOptions.DenyList, networkpolicy.DefaultIPv6DenylistRanges...)
case iputil.IsCIDR(exclude):
npOptions.DenyList = append(npOptions.DenyList, exclude)
case asn.IsASN(exclude):
// update this to use networkpolicy pkg once https://github.com/projectdiscovery/networkpolicy/pull/55 is merged
ips := expandASNInputValue(exclude)
npOptions.DenyList = append(npOptions.DenyList, ips...)
case iputil.IsPort(exclude):
port, _ := strconv.Atoi(exclude)
npOptions.DenyPortList = append(npOptions.DenyPortList, port)
default:
npOptions.DenyList = append(npOptions.DenyList, exclude)
}
}
np, _ := networkpolicy.New(npOptions)
runner := &Runner{
options: options,
stdin: fileutil.HasStdin(),
crawlerOptions: crawlerOptions,
crawler: crawler,
state: &RunnerState{InFlightUrls: mapsutil.NewSyncLockMap[string, struct{}]()},
networkpolicy: np,
}
return runner, nil
}
// Close closes the runner releasing resources
func (r *Runner) Close() error {
return multierr.Combine(
r.crawler.Close(),
r.crawlerOptions.Close(),
)
}
func (r *Runner) SaveState(resumeFilename string) error {
runnerState := r.state
data, _ := json.Marshal(runnerState)
return os.WriteFile(resumeFilename, data, os.ModePerm)
}
func expandCIDRInputValue(value string) []string {
var ips []string
ipsCh, _ := mapcidr.IPAddressesAsStream(value)
for ip := range ipsCh {
ips = append(ips, ip)
}
return ips
}
func expandASNInputValue(value string) []string {
var ips []string
cidrs, _ := asn.GetCIDRsForASNNum(value)
for _, cidr := range cidrs {
ips = append(ips, expandCIDRInputValue(cidr.String())...)
}
return ips
}
================================================
FILE: internal/testutils/helper.go
================================================
package testutils
func CompareOutput(input, expected []string) bool {
if len(input) != len(expected) {
return false
}
for i, v := range input {
if v != expected[i] {
return false
}
}
return true
}
================================================
FILE: internal/testutils/integration.go
================================================
package testutils
import (
"fmt"
"os/exec"
"strings"
)
func RunKatanaBinaryAndGetResults(target string, katanaBinary string, debug bool, args []string) ([]string, error) {
cmd := exec.Command("bash", "-c")
cmdLine := fmt.Sprintf(`echo %s | %s `, target, katanaBinary)
cmdLine += strings.Join(args, " ")
cmd.Args = append(cmd.Args, cmdLine)
data, err := cmd.Output()
if err != nil {
return nil, err
}
parts := []string{}
items := strings.Split(string(data), "\n")
for _, i := range items {
if i != "" {
parts = append(parts, i)
}
}
return parts, nil
}
================================================
FILE: internal/testutils/testutils.go
================================================
package testutils
import (
"strings"
"github.com/projectdiscovery/utils/errkit"
)
type TestCase struct {
Name string
Target string
Args string
Expected []string
CompareFunc func(target string, got []string) error
}
var TestCases = []TestCase{
{
Name: "Headless Browser Without Incognito",
Target: "https://www.hackerone.com/",
Expected: nil,
Args: "-headless -no-incognito -depth 2 -silent -no-sandbox",
CompareFunc: func(target string, got []string) error {
for _, res := range got {
if strings.Contains(res, target) {
return nil
}
}
return errkit.Newf("expected %v target in output, but got %v ", target, strings.Join(got, "\n"))
},
},
}
================================================
FILE: pkg/engine/common/base.go
================================================
package common
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/go-rod/rod"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/engine/parser/files"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/types"
"github.com/projectdiscovery/katana/pkg/utils"
"github.com/projectdiscovery/katana/pkg/utils/queue"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
httputil "github.com/projectdiscovery/utils/http"
mapsutil "github.com/projectdiscovery/utils/maps"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/remeh/sizedwaitgroup"
)
// Shared represents the shared state and configuration used across all crawl sessions.
// It maintains common resources like HTTP headers, cookie jars, known files database,
// and crawler options that are reused for efficiency across multiple crawl operations.
type Shared struct {
Headers map[string]string
KnownFiles *files.KnownFiles
Options *types.CrawlerOptions
Jar *httputil.CookieJar
PathTrie *utils.PathTrie
}
// NewShared creates a new Shared instance with the provided crawler options.
// It initializes the HTTP headers, known files database (if configured), and an empty cookie jar.
// Returns an error if the HTTP client or cookie jar creation fails.
func NewShared(options *types.CrawlerOptions) (*Shared, error) {
shared := &Shared{
Headers: options.Options.ParseCustomHeaders(),
Options: options,
}
if options.Options.KnownFiles != "" {
httpclient, _, err := BuildHttpClient(options.Dialer, options.Options, nil)
if err != nil {
return nil, errkit.Wrap(err, "could not create http client")
}
shared.KnownFiles = files.New(httpclient, options.Options.KnownFiles)
}
// create an empty cookie jar, this is used to store cookies during the crawl
jar, err := httputil.NewCookieJar()
if err != nil {
return nil, errkit.Wrap(err, "could not create cookie jar")
}
shared.Jar = jar
if options.Options.FilterSimilar {
shared.PathTrie = utils.NewPathTrie(options.Options.FilterSimilarThreshold)
}
return shared, nil
}
// Enqueue adds one or more navigation requests to the crawl queue after applying
// validation checks. The method performs the following checks in order:
// 1. URL format validation
// 2. Query parameter handling (if IgnoreQueryParams is enabled)
// 3. Depth filtering - skips URLs exceeding MaxDepth before uniqueness check
// to prevent caching URLs that would be rejected, allowing them to be
// processed if discovered later at valid depths via different paths
// 4. Uniqueness filtering - prevents duplicate URL crawling
// 5. Cycle detection - identifies URLs stuck in redirect loops
// 6. Scope validation - ensures URLs belong to the allowed crawl scope
//
// For in-scope URLs, the method also handles path climbing when enabled,
// extracting and enqueuing parent directory paths.
// Out-of-scope URLs are sent to output if DisplayOutScope is enabled.
func (s *Shared) Enqueue(queue *queue.Queue, navigationRequests ...*navigation.Request) {
for _, nr := range navigationRequests {
if nr.URL == "" || !utils.IsURL(nr.URL) {
if s.Options.Options.OnSkipURL != nil {
s.Options.Options.OnSkipURL(nr.URL)
}
continue
}
reqUrl := nr.RequestURL()
if s.Options.Options.IgnoreQueryParams {
reqUrl = utils.ReplaceAllQueryParam(reqUrl, "")
}
if s.Options.Options.FilterSimilar {
reqUrl = utils.FingerprintURL(reqUrl, s.PathTrie)
}
// Skip adding to the crawl queue when the maximum depth is exceeded.
// Must be done before checking uniqueness to avoid caching item that will be skipped
// to handle them if faced on lower depth via another path.
if nr.Depth > s.Options.Options.MaxDepth {
continue
}
// Ignore blank URL items and only work on unique items
if !s.Options.UniqueFilter.UniqueURL(reqUrl) && len(nr.CustomFields) == 0 {
continue
}
// - URLs stuck in a loop
if s.Options.UniqueFilter.IsCycle(nr.RequestURL()) {
continue
}
// skip crawling if the endpoint is not in scope
inScope := s.ValidateScope(nr.URL, nr.RootHostname)
if !inScope {
// if the user requested anyway out of scope items
// they are sent to output without visiting
if s.Options.Options.DisplayOutScope {
s.Output(nr, nil, ErrOutOfScope)
}
continue
}
queue.Push(nr, nr.Depth)
if s.Options.Options.PathClimb {
extractedParentURLs := utils.ExtractParentPaths(nr.URL)
for _, extractedParentURL := range extractedParentURLs {
if !utils.IsURL(extractedParentURL) {
continue
}
checkURL := extractedParentURL
if s.Options.Options.FilterSimilar {
checkURL = utils.FingerprintURL(checkURL, s.PathTrie)
}
if !s.Options.UniqueFilter.UniqueURL(checkURL) {
continue
}
if !s.ValidateScope(extractedParentURL, nr.RootHostname) {
continue
}
parentDepth := nr.Depth
if parentDepth > 0 {
parentDepth--
}
parentReq := &navigation.Request{
Method: nr.Method,
URL: extractedParentURL,
Depth: parentDepth,
RootHostname: nr.RootHostname,
Source: nr.Source,
Tag: "path-climb",
}
queue.Push(parentReq, parentDepth)
}
}
}
}
// ValidateScope checks whether a given URL is within the allowed crawling scope
// based on the configured scope rules and the root hostname.
// Returns true if the URL passes scope validation, false otherwise.
func (s *Shared) ValidateScope(URL string, root string) bool {
parsed, err := urlutil.Parse(URL)
if err != nil {
gologger.Warning().Msgf("failed to parse url while validating scope: %v", err)
return false
}
scopeValidated, err := s.Options.ScopeManager.Validate(parsed.URL, root)
return err == nil && scopeValidated
}
// Output writes a crawl result to the configured output writer.
// It creates a Result object containing the navigation request, response (if any),
// and error information (if any), then writes it to the output writer.
// If an OnResult callback is configured and output writing succeeds, the callback is invoked.
func (s *Shared) Output(navigationRequest *navigation.Request, navigationResponse *navigation.Response, err error) {
var errData string
if err != nil {
errData = err.Error()
}
// Write the found result to output
result := &output.Result{
Timestamp: time.Now(),
Request: navigationRequest,
Response: navigationResponse,
Error: errData,
}
outputErr := s.Options.OutputWriter.Write(result)
if s.Options.Options.OnResult != nil && outputErr == nil {
s.Options.Options.OnResult(*result)
}
}
// CrawlSession represents an active crawling session for a specific target URL.
// It maintains the session context, cancellation function, parsed URL information,
// the request queue, and HTTP/browser clients needed for the crawl operation.
type CrawlSession struct {
Ctx context.Context
CancelFunc context.CancelFunc
URL *url.URL
Hostname string
Queue *queue.Queue
HttpClient *retryablehttp.Client
Browser *rod.Browser
}
// NewCrawlSessionWithURL creates and initializes a new crawl session for the specified URL.
// It performs the following initialization steps:
// 1. Creates a context with optional timeout based on CrawlDuration setting
// 2. Parses the target URL and extracts the hostname
// 3. Initializes the request queue with the configured strategy
// 4. Enqueues the initial URL and any known files for the target
// 5. Sets up the HTTP client with response parsing callbacks
//
// Returns the initialized CrawlSession or an error if initialization fails.
func (s *Shared) NewCrawlSessionWithURL(URL string) (*CrawlSession, error) {
ctx, cancel := context.WithCancel(context.Background())
if s.Options.Options.CrawlDuration.Seconds() > 0 {
//nolint
ctx, cancel = context.WithTimeout(ctx, s.Options.Options.CrawlDuration)
}
parsed, err := urlutil.Parse(URL)
if err != nil {
cancel()
return nil, errkit.Wrap(err, "could not parse root URL")
}
hostname := parsed.Hostname()
queue, err := queue.New(s.Options.Options.Strategy, s.Options.Options.Timeout)
if err != nil {
cancel()
return nil, err
}
queue.Push(&navigation.Request{Method: http.MethodGet, URL: URL, Depth: 0, SkipValidation: true}, 0)
if s.KnownFiles != nil {
navigationRequests, err := s.KnownFiles.Request(URL)
if err != nil {
gologger.Warning().Msgf("Could not parse known files for %s: %s\n", URL, err)
}
s.Enqueue(queue, navigationRequests...)
}
httpclient, _, err := BuildHttpClient(s.Options.Dialer, s.Options.Options, func(resp *http.Response, depth int) {
body, _ := io.ReadAll(resp.Body)
reader, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))
var technologyKeys []string
if s.Options.Wappalyzer != nil {
technologies := s.Options.Wappalyzer.Fingerprint(resp.Header, body)
technologyKeys = mapsutil.GetKeys(technologies)
}
navigationResponse := &navigation.Response{
Depth: depth + 1,
RootHostname: hostname,
Resp: resp,
Body: string(body),
Reader: reader,
Technologies: technologyKeys,
StatusCode: resp.StatusCode,
Headers: utils.FlattenHeaders(resp.Header),
KnowledgeBase: s.Options.ClassifyPage(string(body)),
}
navigationRequests := s.Options.Parser.ParseResponse(navigationResponse)
s.Enqueue(queue, navigationRequests...)
})
if err != nil {
cancel()
return nil, errkit.Wrap(err, "could not create http client")
}
crawlSession := &CrawlSession{
Ctx: ctx,
CancelFunc: cancel,
URL: parsed.URL,
Hostname: hostname,
Queue: queue,
HttpClient: httpclient,
}
return crawlSession, nil
}
// DoRequestFunc is a function type for executing navigation requests.
// Implementations should perform the actual HTTP request or browser navigation
// and return the response or an error. This allows different crawling strategies
// (standard HTTP vs. headless browser) to provide their own request logic.
type DoRequestFunc func(crawlSession *CrawlSession, req *navigation.Request) (*navigation.Response, error)
// Do executes the main crawling loop for the given crawl session.
// It processes items from the queue concurrently (respecting the Concurrency limit),
// validates each request (URL format, path filters, scope), applies rate limiting
// and delays, executes the request using the provided doRequest function, writes
// results to output, and enqueues any newly discovered URLs from responses.
//
// The method returns when the queue is empty or the session context is cancelled
// (due to timeout or manual cancellation). Returns an error if the context is cancelled.
func (s *Shared) Do(crawlSession *CrawlSession, doRequest DoRequestFunc) error {
wg := sizedwaitgroup.New(s.Options.Options.Concurrency)
for item := range crawlSession.Queue.Pop() {
if ctxErr := crawlSession.Ctx.Err(); ctxErr != nil {
return ctxErr
}
req, ok := item.(*navigation.Request)
if !ok {
continue
}
if !utils.IsURL(req.URL) {
if s.Options.Options.OnSkipURL != nil {
s.Options.Options.OnSkipURL(req.URL)
}
gologger.Debug().Msgf("`%v` not a url. skipping", req.URL)
continue
}
if !s.Options.ValidatePath(req.URL) {
gologger.Debug().Msgf("`%v` filtered path. skipping", req.URL)
continue
}
inScope, scopeErr := s.Options.ValidateScope(req.URL, crawlSession.Hostname)
if scopeErr != nil {
gologger.Debug().Msgf("Error validating scope for `%v`: %v. skipping", req.URL, scopeErr)
continue
}
if !req.SkipValidation && !inScope {
gologger.Debug().Msgf("`%v` not in scope. skipping", req.URL)
continue
}
wg.Add()
// gologger.Debug().Msgf("Visiting: %v", req.URL) // not sure if this is needed
go func() {
defer wg.Done()
s.Options.RateLimit.Take()
// Delay if the user has asked for it
if s.Options.Options.Delay > 0 {
time.Sleep(time.Duration(s.Options.Options.Delay) * time.Second)
}
resp, err := doRequest(crawlSession, req)
if inScope {
s.Output(req, resp, err)
}
if err != nil {
gologger.Warning().Msgf("Could not request seed URL %s: %s\n", req.URL, err)
outputError := &output.Error{
Timestamp: time.Now(),
Endpoint: req.RequestURL(),
Source: req.Source,
Error: err.Error(),
}
_ = s.Options.OutputWriter.WriteErr(outputError)
return
}
if resp == nil || resp.Resp == nil || resp.Reader == nil {
return
}
if s.Options.Options.DisableRedirects && resp.IsRedirect() {
return
}
navigationRequests := s.Options.Parser.ParseResponse(resp)
s.Enqueue(crawlSession.Queue, navigationRequests...)
}()
}
wg.Wait()
return nil
}
================================================
FILE: pkg/engine/common/error.go
================================================
package common
import "errors"
var ErrOutOfScope = errors.New("out of scope")
================================================
FILE: pkg/engine/common/http.go
================================================
package common
import (
"context"
"crypto/tls"
"net"
"net/http"
"net/url"
"time"
"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/fastdialer/fastdialer/ja3/impersonate"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/types"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
proxyutil "github.com/projectdiscovery/utils/proxy"
)
type RedirectCallback func(resp *http.Response, depth int)
// BuildHttpClient builds a http client based on a profile
func BuildHttpClient(dialer *fastdialer.Dialer, options *types.Options, redirectCallback RedirectCallback) (*retryablehttp.Client, *fastdialer.Dialer, error) {
// Single Host
retryablehttpOptions := retryablehttp.DefaultOptionsSingle
retryablehttpOptions.RetryMax = options.Retries
transport := &http.Transport{
DialContext: dialer.Dial,
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
if options.TlsImpersonate {
return dialer.DialTLSWithConfigImpersonate(ctx, network, addr, &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10}, impersonate.Random, nil)
}
return dialer.DialTLS(ctx, network, addr)
},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
MaxConnsPerHost: 100,
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
InsecureSkipVerify: true,
},
DisableKeepAlives: false,
}
// Attempts to overwrite the dial function with the socks proxied version
if proxyURL, err := url.Parse(options.Proxy); options.Proxy != "" && err == nil {
if ok, err := proxyutil.IsBurp(options.Proxy); err == nil && ok {
transport.TLSClientConfig.MaxVersion = tls.VersionTLS12
}
transport.Proxy = http.ProxyURL(proxyURL)
}
client := retryablehttp.NewWithHTTPClient(&http.Client{
Transport: transport,
Timeout: time.Duration(options.Timeout) * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if options.DisableRedirects {
return http.ErrUseLastResponse
}
if len(via) == 10 {
return errkit.New("stopped after 10 redirects")
}
depth, ok := req.Context().Value(navigation.Depth{}).(int)
if !ok {
depth = 2
}
if redirectCallback != nil {
redirectCallback(req.Response, depth)
}
return nil
},
}, retryablehttpOptions)
client.CheckRetry = retryablehttp.HostSprayRetryPolicy()
return client, dialer, nil
}
================================================
FILE: pkg/engine/engine.go
================================================
package engine
type Engine interface {
Crawl(string) error
Close() error
}
================================================
FILE: pkg/engine/headless/TODOS.md
================================================
# 🗺️ Headless-Crawler Road-Map
---
## 🥇 Core Improvements (High-impact / first passes)
- [ ] After clicking on elements there isn't enough wait time to reflect SPA navigation
- [ ] **Replace exact DOM hash with perceptual fingerprint**
- Stage 1: keep current SHA-256 on stripped DOM for cheap exact-match.
- Stage 2: compute 64-bit SimHash/MinHash over 3–4-word shingles of the stripped DOM; treat pages equal when Hamming distance ≤ 3 bits.
- Stage 3 (optional): if SimHash inconclusive, take a low-res screenshot and compare pHash/dHash.
- Store `ExactHash` & `FuzzyHash`; update graph comparison logic.
- [ ] **Robust “page ready” detector**
- Inject `MutationObserver` + `requestIdleCallback`.
- Resolve when:
* no DOM mutation for N ms **and**
* `location.href`/`history.length`/`` stable.
- Wrap as `page.WaitForRouteChange()` and replace `WaitPageLoadHeuristics`.
- [ ] **Lazy-load / infinite-scroll support**
- Loop: `scrollBy(0, viewportHeight*0.9)` until `scrollHeight` stops growing.
- Fallback: IntersectionObserver on a sentinel div.
- [ ] **Capture all secondary resource navigations**
- Enable `FetchEnable` + `Network.*` events.
- Record: XHR/Fetch URLs, WebSocket, EventSource, Service-Worker scripts.
- Feed new, in-scope URLs into the crawl queue (dedup by host/path).
---
## 🥈 Mid-term Enhancements
- [ ] **Dynamic form-filling**
- Generate values based on `type`, `pattern`, `min`, `max`, `maxlength`, `required`.
- Pluggable `ValueProvider` interface for site-specific logic.
- [ ] **Site adapters / hooks**
- Allow user-supplied Go or JS snippets per hostname (e.g. dismiss cookie wall, auto login, click “load more”).
- [ ] **Concurrent tab execution**
- Worker pool consuming the action queue.
- Use multiple `rod.Page` instances (shared browser) – make `CrawlGraph` concurrency-safe.
- [ ] **Smart time-out & retry budgets**
- Adaptive timeout: first nav longer, later ones shorter; one automatic reload on stall.
- [ ] **Viewport variants**
- Crawl again at typical mobile (390×844) & tablet (768×1024) sizes to reveal responsive content.
- [ ] **Memory & process recycling**
- Close background tabs after use.
- If Chrome RSS > threshold, restart browser (persist cookies if needed).
- [ ] **Anti-bot hardening**
- Spoof fonts, canvas & audio contexts.
- Rotate realistic UA strings, languages, `hardwareConcurrency`.
- Optional headful mode via XVFB to enable GPU paths.
---
## 🥉 Nice-to-have / Advanced
- [ ] **Export crawl sessions**
- HAR or WARC output in parallel to existing JSON.
- [ ] **JS coverage tracking**
- `Profiler.startPreciseCoverage` → know which scripts never executed.
- [ ] **Metrics & health**
- Prometheus counters (pages, active tabs, JS errors, nav errors).
- `/debug/pprof` enabled by default.
- [ ] **TLS / proxy flexibility**
- Accept custom CA bundle, client certs, upstream proxy rotation.
- [ ] **Sandboxing & security**
- Run Chrome under seccomp / user-namespaces or separate UID/GID automatically.
- [ ] **Graceful crash recovery**
- Detect `Page.crashed` / `Browser.disconnected`; re-spawn browser, resume queue.
----------------------------------
Claude OPUS info below:
## 🎯 Critical Bug Fixes & Edge Cases
- [ ] **Handle iframe content extraction**
- Cross-origin iframe detection and flagging
- Same-origin iframe DOM traversal
- Nested iframe support (up to N levels)
- [ ] **WebComponent & Shadow DOM support**
- Detect custom elements with shadow roots
- Traverse open shadow DOMs for form/link discovery
- Handle slot-based content projection
- [ ] **Multi-window/tab detection**
- Track `window.open()` calls that bypass current hooks
- Handle popup windows that close parent
- Manage tab focus for proper event firing
## 🔐 Authentication & Session Management
- [ ] **Auth state detection**
- Detect login/logout UI patterns
- Monitor cookie changes for session tracking
- Implement auth health checks between actions
- [ ] **Multi-step auth flows**
- OAuth redirect handling
- 2FA/MFA detection and waiting
- SAML/SSO flow support
- [ ] **Session persistence**
- Save/restore cookies between crawls
- Handle JWT token refresh
- Detect and handle session timeouts
## 🎪 Advanced Interaction Patterns
- [ ] **Complex UI interactions**
- Drag & drop detection and execution
- File upload with generated test files
- Multi-select and combo-box handling
- Date/time picker interaction
- [ ] **Keyboard navigation support**
- Tab-order based discovery
- Keyboard shortcut detection (Ctrl+K, etc.)
- Access key enumeration
- [ ] **Touch/mobile gestures**
- Swipe detection for mobile views
- Long-press context menus
- Pinch-to-zoom aware navigation
## 📊 Analytics & Monitoring
- [ ] **Performance metrics**
- Page load time tracking
- JavaScript execution overhead
- Memory usage per page state
- Network request waterfalls
- [ ] **Crawl quality metrics**
- Code coverage per domain
- Unique vs duplicate state ratio
- Action success/failure rates
- Depth distribution analysis
- [ ] **Error tracking**
- JavaScript console error capture
- Network error categorization
- CSP violation logging
- Failed action root cause analysis
## 🧠 Smart Crawling Features
- [ ] **ML-based duplicate detection**
- Train model on visual similarity
- Semantic HTML structure comparison
- Learn site-specific patterns
- [ ] **Priority queue optimization**
- High-value path prediction
- Anomaly detection for interesting states
- Dynamic depth adjustment based on yield
- [ ] **State space reduction**
- Identify and prune redundant actions
- Detect pagination patterns
- Group similar forms (search variations)
## 🛡️ Security & Compliance
- [ ] **CAPTCHA handling**
- Detection of common CAPTCHA providers
- Integration points for solving services
- Graceful degradation strategies
- [ ] **Rate limiting & politeness**
- Per-domain request throttling
- Respect robots.txt for headless
- Adaptive delays based on response times
- [ ] **Privacy compliance**
- PII detection in forms
- GDPR banner interaction
- Data retention policies
## 🔌 Integration Features
- [ ] **API extraction**
- GraphQL query/mutation detection
- REST endpoint parameter learning
- WebSocket message format detection
- [ ] **Export formats**
- OpenAPI spec generation from discoveries
- Postman collection export
- Burp Suite state file compatibility
- [ ] **Workflow recording**
- Playwright/Puppeteer script generation
- Selenium IDE format export
- Custom DSL for replay
## 🚀 Performance Optimizations
- [ ] **Rendering optimizations**
- Disable images/fonts for text-only analysis
- Viewport-based lazy rendering
- CPU throttling for battery saving
- [ ] **Caching layer**
- DOM diff caching
- Screenshot perceptual hashes
- JavaScript execution results
- [ ] **Distributed crawling**
- Work queue distribution
- State synchronization protocol
- Result aggregation pipeline
## 🔧 Developer Experience
- [ ] **Debug tooling**
- Live crawl visualization
- State graph explorer UI
- Action replay debugger
- [ ] **Configuration management**
- Per-site config profiles
- A/B testing different strategies
- Hot-reload of site adapters
- [ ] **Testing infrastructure**
- Headless crawler unit tests
- Integration tests with test sites
- Regression detection suite
state.go is the crawler’s “state-manager”.
Everything else in the headless package (browser wrappers, normalizer, graph, diagnostics) either feeds data into it or asks it to restore a known state.
To make the crawler scalable, reliable and de-dupe friendly the file should be responsible for exactly three things:
1. Build a reproducible fingerprint (“state ID”) for the current page.
2. Persist the surrounding metadata that we need to replay that state later.
3. Provide deterministic, cheapest-first logic to get back to any recorded state.
Below is a complete design that meets those goals and leaves room for future TODOs.
────────────────────────────────────────────────────────────────────────────
1. Fingerprint strategy (page → id)
────────────────────────────────────────────────────────────────────────────
A. Canonical DOM extraction
• Use the existing domNormalizer (strip scripts, styles, dynamic IDs etc.).
• Remove all transient event-attributes (`onclick`, `onmouseover`, …).
• Collapse whitespace → single space.
B. Two-tier hash
• ExactHash = SHA-256(strippedDOM).
• FuzzyHash = SimHash64(4-word shingles of strippedDOM).
• Treat states equal if
- ExactHash matches, or
- Hamming(FuzzyHash, other.FuzzyHash) ≤ 3 bits.
• Persist both; the graph layer deduplicates on (ExactHash || close-enough FuzzyHash).
C. Optional visual fallback
• If comparison is inconclusive (≥ 4 bit distance but DOM len < 1 MiB)
→ low-res screenshot, pHash/dHash → same threshold logic.
• Executed lazily to avoid perf hit.
Resulting struct:
type PageState struct {
ExactHash string // always present
FuzzyHash uint64 // present if SimHash computed
URL string
Title string
Depth int
StrippedDOM string
NavigationAction *Action // edge that produced this state
Timestamp time.Time
}
–––– Advantages
• SimHash makes minor DOM variations (ads, CSRF tokens) resolve to the same state, reducing graph size.
• Screenshot hash catches SPA view switches that don’t touch the DOM tree much but look different.
────────────────────────────────────────────────────────────────────────────
2. Metadata collection (page → PageState)
────────────────────────────────────────────────────────────────────────────
Algorithm newPageState(page, causingAction):
1. Grab `page.Info()`; bail out if URL is empty or about:blank.
2. outerHTML := page.HTML().
3. stripped := domNormalizer.Apply(outerHTML).
4. Build PageState as above.
5. Compute hashes as described.
6. Diagnostics hook (save stripped DOM, screenshots, etc.).
7. Return the fully populated PageState.
Edge cases handled:
• Empty page → custom ErrEmptyPage (already present).
• Non-deterministic DOM normalizer failure → bubbled up with context.
────────────────────────────────────────────────────────────────────────────
3. Return-to-origin algorithm (current page, targetOriginID) → (pageID, error)
────────────────────────────────────────────────────────────────────────────
Keep the existing three-level approach but hard-code their priority and exit conditions.
Step 0 Fast-fail: if currentID == target → done.
Step 1 Element re-use
• If `action.Element` is non-nil, locate by XPath, ensure Visible & Interactable,
*plus* DOM equality check under the canonicalizer to avoid false positives.
• If match, return targetOriginID.
Step 2 Browser history
• page.GetNavigationHistory()
• Walk back until (url == origin.URL && title == origin.Title).
• Limit: max 10 steps to avoid long loops.
• After each back() call wait with WaitForRouteChange() (new detector described below).
• Recompute fingerprint; if equal (exact or fuzzy) → success.
Step 3 Graph shortest path
• crawlerGraph.ShortestPath(currentID, targetID).
• If unreachable, retry from emptyPageHash (fresh tab).
• Execute each Action; after each, WaitForRouteChange().
• After final step verify state (same equality logic as Step 2).
• Failure → ErrNoNavigationPossible.
Enhancements
• Cache the computed “distance” between two states; next call can skip graph search.
• Record statistics (#navigationBackSuccessByMethod) to tune the priority order.
────────────────────────────────────────────────────────────────────────────
4. “Page ready” detector (WaitForRouteChange)
────────────────────────────────────────────────────────────────────────────
Replace the brittle WaitPageLoadHeuristics with:
Injected JS once per tab:
const idle = () => new Promise(res => {
const done = () => { obs.disconnect(); res(); };
let t;
const reset = () => { clearTimeout(t); t = setTimeout(done, 300); };
const obs = new MutationObserver(reset);
obs.observe(document, {subtree: true, childList: true, attributes: true});
reset();
});
window.__katanaReady = () => Promise.all([
idle(),
new Promise(r => requestIdleCallback(r, {timeout: 5000}))
]);
Go side:
func (p *BrowserPage) WaitForRouteChange() error {
ctx, cancel := context.WithTimeout(p.ctx, 15*time.Second)
defer cancel()
return rod.Try(func() {
p.Eval(ctx, `await window.__katanaReady()`)
})
}
Detects route changes, SPA navigations, AJAX content, infinite scroll “settling”, etc.
────────────────────────────────────────────────────────────────────────────
5. Extensibility hooks
────────────────────────────────────────────────────────────────────────────
• FingerprintStrategy interface so users can plug in custom SimHash/Screenshot logic.
• ValueProvider & SiteAdapter interfaces already planned can depend on PageState to decide actions.
• Diagnostics sink gets PageState + serialized Action graph for offline visualizer.
────────────────────────────────────────────────────────────────────────────
6. Migration plan
────────────────────────────────────────────────────────────────────────────
1. Stage 1 (quick): keep old sha256 flow, introduce struct fields & interface but stub SimHash.
2. Stage 2: integrate open-source SimHash library, enable fuzzy comparator in graph.
3. Stage 3: optional pHash path guarded by feature flag.
4. Replace WaitPageLoadHeuristics with WaitForRouteChange().
5. Add metrics around navigation-back success.
This architecture keeps state.go focused, removes hidden coupling, and sets up the crawler for future road-map items (concurrent tabs, adapters, ML dedup, etc.) while remaining incremental enough to merge in small PRs.
================================================
FILE: pkg/engine/headless/browser/browser.go
================================================
package browser
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httputil"
"os"
"os/user"
"strconv"
"strings"
"sync"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
"github.com/go-rod/rod/lib/launcher/flags"
"github.com/go-rod/rod/lib/proto"
rodutils "github.com/go-rod/rod/lib/utils"
"github.com/pkg/errors"
"github.com/projectdiscovery/katana/pkg/engine/headless/browser/cookie"
"github.com/projectdiscovery/katana/pkg/engine/headless/browser/stealth"
"github.com/projectdiscovery/katana/pkg/engine/headless/js"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/utils"
"github.com/rs/xid"
)
// Launcher is a high level controller to launch browsers
// and do the execution on them.
type Launcher struct {
browserPool rod.Pool[BrowserPage]
opts LauncherOptions
}
// LauncherOptions contains options for the launcher
type LauncherOptions struct {
ChromiumPath string
MaxBrowsers int
PageMaxTimeout time.Duration
ShowBrowser bool
NoSandbox bool
Proxy string
SlowMotion bool
Trace bool
CookieConsentBypass bool
ChromeUser *user.User // optional chrome user to use
ScopeValidator ScopeValidator
RequestCallback func(*output.Result)
}
type ScopeValidator func(string) bool
// NewLauncher returns a new launcher instance
func NewLauncher(opts LauncherOptions) (*Launcher, error) {
l := &Launcher{
opts: opts,
browserPool: rod.NewPool[BrowserPage](opts.MaxBrowsers),
}
return l, nil
}
func (l *Launcher) ScopeValidator() ScopeValidator {
return l.opts.ScopeValidator
}
func (l *Launcher) launchBrowserWithDataDir(userDataDir string) (*rod.Browser, error) {
chromeLauncher := launcher.New().
Leakless(true).
Set("disable-gpu", "true").
Set("ignore-certificate-errors", "true").
Set("disable-crash-reporter", "true").
Set("disable-notifications", "true").
Set("hide-scrollbars", "true").
Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).
Set("mute-audio", "true").
Set("incognito", "true").
Delete("use-mock-keychain").
Delete("disable-ipc-flooding-protection").
Headless(true)
for _, flag := range headlessFlags {
splitted := strings.TrimPrefix(flag, "--")
values := strings.Split(splitted, "=")
if len(values) == 2 {
chromeLauncher = chromeLauncher.Set(flags.Flag(values[0]), strings.Split(values[1], ",")...)
} else {
chromeLauncher = chromeLauncher.Set(flags.Flag(splitted), "true")
}
}
if l.opts.Proxy != "" {
chromeLauncher = chromeLauncher.Proxy(l.opts.Proxy)
}
if l.opts.NoSandbox {
chromeLauncher = chromeLauncher.NoSandbox(true)
}
if l.opts.ShowBrowser {
chromeLauncher = chromeLauncher.Headless(false)
}
if l.opts.ChromiumPath != "" {
chromeLauncher = chromeLauncher.Bin(l.opts.ChromiumPath)
}
if userDataDir != "" {
chromeLauncher = chromeLauncher.UserDataDir(userDataDir)
}
launcherURL, err := chromeLauncher.Launch()
if err != nil {
return nil, err
}
browser := rod.New().
ControlURL(launcherURL)
if l.opts.Trace {
browser = browser.Trace(true)
}
if l.opts.SlowMotion {
browser = browser.SlowMotion(1 * time.Second)
}
if browserErr := browser.Connect(); browserErr != nil {
return nil, browserErr
}
return browser, nil
}
// Close closes the launcher
func (l *Launcher) Close() {
l.browserPool.Cleanup(func(b *BrowserPage) {
b.cancel()
b.CloseBrowserPage()
})
close(l.browserPool)
}
// BrowserPage is a combination of a browser and a page
type BrowserPage struct {
*rod.Page
Browser *rod.Browser
cancel context.CancelFunc
userDataDir string
launcher *Launcher
}
// WaitOptions controls how WaitPageLoadHeurisitics determines navigation completion.
// All durations are conservative defaults and can be tuned later via package-level variables
// or future setter methods (kept simple here to avoid breaking public API).
type WaitOptions struct {
URLPollInterval time.Duration // interval between successive URL polls
URLPollTimeout time.Duration // how long to keep polling before giving up on URL change
PostChangeWait time.Duration // small grace period after URL change for late requests
IdleWait time.Duration // network-idle window when no URL change happened
DOMStableWait time.Duration // DOM-stable window (used after idle)
MaxTimeout time.Duration // absolute upper bound for all waiting
}
// defaultWaitOptions are derived from empirical measurements on modern SPA pages.
var defaultWaitOptions = WaitOptions{
URLPollInterval: 100 * time.Millisecond,
URLPollTimeout: 2 * time.Second,
PostChangeWait: 300 * time.Millisecond,
IdleWait: 1 * time.Second,
DOMStableWait: 1 * time.Second,
MaxTimeout: 15 * time.Second,
}
// WaitPageLoadHeurisitics waits for the page to load using multiple heuristics.
// Strategy order:
// 1. Wait for initial load event (covers classic navigation & first paint).
// 2. Poll for a URL change – the strongest signal on SPAs with client-side routing.
// 3. If URL changes, wait a short grace period + network-idle window.
// 4. If URL doesn't change, fall back to network-idle + DOM-stable windows.
//
// This keeps fast pages fast while still succeeding on noisy, long-running SPAs.
func (b *BrowserPage) WaitPageLoadHeurisitics() error {
opts := defaultWaitOptions
chained := b.Timeout(opts.MaxTimeout)
// 1. Wait for the basic load event (DOMContentLoaded / load).
_ = chained.WaitLoad()
// 2. Capture the current URL so we can detect route changes.
urlVal, _ := b.Eval("() => window.location.href")
startURL := ""
if urlVal != nil {
startURL = urlVal.Value.Str()
}
// 3. Poll for a different URL for up to URLPollTimeout.
urlChanged := false
if startURL != "" {
pollCount := int(opts.URLPollTimeout / opts.URLPollInterval)
for i := 0; i < pollCount; i++ {
time.Sleep(opts.URLPollInterval)
cur, err := b.Eval("() => window.location.href")
if err == nil && cur != nil && cur.Value.Str() != startURL {
urlChanged = true
break
}
}
}
if urlChanged {
// 4a. URL changed – short grace period then network idle & done.
_ = chained.WaitIdle(opts.PostChangeWait)
return nil
}
// 4b. URL didn't change – fall back to broader heuristics.
_ = chained.WaitIdle(opts.IdleWait)
_ = b.WaitNewStable(opts.DOMStableWait)
return nil
}
// WaitPageLoadHeuristicsFallback provides the enhanced timeouts for complex navigation
func (b *BrowserPage) WaitPageLoadHeuristicsFallback() error {
chainedTimeout := b.Timeout(20 * time.Second)
_ = chainedTimeout.WaitLoad()
_ = chainedTimeout.WaitIdle(4 * time.Second)
_ = b.WaitNewStable(2 * time.Second)
return nil
}
// WaitStable waits until the page is stable for d duration.
func (p *BrowserPage) WaitNewStable(d time.Duration) error {
// Enforce an upper-bound on how long we will wait for the page to become
// stable. We simply reuse the heuristic window (d) and give the combined
// operation 2× that duration. This guarantees that callers will be
// released after a finite time instead of blocking forever when a page
// keeps a long-lived connection open (analytics beacons, WebSockets, etc.).
chained := p.Timeout(2 * d)
var err error
setErr := sync.Once{}
rodutils.All(func() {
e := chained.WaitLoad()
setErr.Do(func() { err = e })
}, func() {
chained.WaitRequestIdle(d, nil, []string{}, nil)()
}, func() {
e := chained.WaitDOMStable(d, 0)
setErr.Do(func() { err = e })
})()
return err
}
func (l *Launcher) createBrowserPageFunc() (*BrowserPage, error) {
// Create unique temp userDataDir for this browser instance
var tempDir string
shouldCleanup := true
// Deferred cleanup function that will be set after tempDir creation
defer func() {
if shouldCleanup && tempDir != "" {
_ = os.RemoveAll(tempDir)
}
}()
if l.opts.ChromeUser != nil {
var err error
tempDir, err = os.MkdirTemp(l.opts.ChromeUser.HomeDir, "chrome-data-*")
if err != nil {
return nil, errors.Wrap(err, "could not create temporary chrome data directory")
}
uid, err := strconv.Atoi(l.opts.ChromeUser.Uid)
if err != nil {
return nil, errors.Wrap(err, "invalid user ID")
}
gid, err := strconv.Atoi(l.opts.ChromeUser.Gid)
if err != nil {
return nil, errors.Wrap(err, "invalid group ID")
}
if err := os.Chown(tempDir, uid, gid); err != nil {
return nil, errors.Wrap(err, "could not change ownership of chrome data directory")
}
} else {
var err error
tempDir, err = os.MkdirTemp("", "katana-chrome-data-*")
if err != nil {
return nil, errors.Wrap(err, "could not create temporary chrome data directory")
}
}
browser, err := l.launchBrowserWithDataDir(tempDir)
if err != nil {
return nil, err
}
page, err := browser.Page(proto.TargetCreateTarget{})
if err != nil {
return nil, errors.Wrap(err, "could not create new page")
}
successfulPageCreation := false
defer func() {
if !successfulPageCreation {
_ = page.Close()
_ = browser.Close()
}
}()
page = page.Sleeper(func() rodutils.Sleeper {
return backoffCountSleeper(100*time.Millisecond, 1*time.Second, 3, func(d time.Duration) time.Duration {
return d * 1
})
})
ctx := page.GetContext()
cancelCtx, cancel := context.WithCancel(ctx)
page = page.Context(cancelCtx)
browserPage := &BrowserPage{
Page: page,
Browser: browser,
launcher: l,
cancel: cancel,
userDataDir: tempDir,
}
if err := browserPage.handlePageDialogBoxes(); err != nil {
return nil, err
}
// Add stealth evasion JS
_, err = page.EvalOnNewDocument(stealth.JS)
if err != nil {
return nil, errors.Wrap(err, "could not initialize stealth")
}
err = js.InitJavascriptEnv(page)
if err != nil {
return nil, errors.Wrap(err, "could not initialize javascript env")
}
// Success - cancel the deferred cleanup
successfulPageCreation = true
shouldCleanup = false
return browserPage, nil
}
// GetPageFromPool returns a page from the pool
func (l *Launcher) GetPageFromPool() (*BrowserPage, error) {
browserPage, err := l.browserPool.Get(l.createBrowserPageFunc)
if err != nil {
return nil, err
}
// TODO: should we check if the browser is alive because sometimes it
// might die?
return browserPage, nil
}
// backoffCountSleeper returns a sleeper that uses backoff strategy but stops after max attempts.
// It combines the functionality of BackoffSleeper and CountSleeper.
func backoffCountSleeper(initInterval, maxInterval time.Duration, maxAttempts int, algorithm func(time.Duration) time.Duration) rodutils.Sleeper {
backoff := rodutils.BackoffSleeper(initInterval, maxInterval, algorithm)
count := rodutils.CountSleeper(maxAttempts)
return rodutils.EachSleepers(backoff, count)
}
func (b *BrowserPage) handlePageDialogBoxes() error {
err := proto.FetchEnable{
Patterns: []*proto.FetchRequestPattern{
{
URLPattern: "*",
RequestStage: proto.FetchRequestStageResponse,
},
},
}.Call(b.Page)
if err != nil {
return errors.Wrap(err, "could not enable fetch domain")
}
go b.EachEvent(
func(e *proto.PageJavascriptDialogOpening) {
_ = proto.PageHandleJavaScriptDialog{
Accept: true,
PromptText: xid.New().String(),
}.Call(b.Page)
},
func(e *proto.FetchRequestPaused) {
if b.launcher.opts.CookieConsentBypass {
// Check if request should be blocked by cookie consent rules
var originStr string
if origin, ok := e.Request.Headers["Origin"]; ok {
originStr = origin.Str()
}
if cookie.ShouldBlockRequest(e.Request.URL, e.ResourceType, originStr) {
_ = proto.FetchFailRequest{
RequestID: e.RequestID,
ErrorReason: proto.NetworkErrorReasonBlockedByClient,
}.Call(b.Page)
return
}
}
if e.ResponseStatusCode == nil || e.ResponseErrorReason != "" || (*e.ResponseStatusCode >= 301 && *e.ResponseStatusCode <= 308) {
if err := fetchContinueRequest(b.Page, e); err != nil {
slog.Warn("fetchContinueRequest failed", "error", err)
}
return
}
body, err := fetchGetResponseBody(b.Page, e)
if err != nil {
// Continue the request even if we can't get the body
if err := fetchContinueRequest(b.Page, e); err != nil {
slog.Warn("fetchContinueRequest failed", "error", err)
}
return
}
if err := fetchContinueRequest(b.Page, e); err != nil {
slog.Warn("fetchContinueRequest failed", "error", err)
}
httpreq, err := netHTTPRequestFromProto(e.Request)
if err != nil {
return
}
rawBytesRequest, _ := httputil.DumpRequestOut(httpreq, true)
req := navigation.Request{
Method: httpreq.Method,
URL: httpreq.URL.String(),
Body: e.Request.PostData,
Headers: utils.FlattenHeaders(httpreq.Header),
Raw: string(rawBytesRequest),
}
httpresp := netHTTPResponseFromProto(e, body)
httpresp.Request = httpreq
rawBytesResponse, _ := httputil.DumpResponse(httpresp, true)
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))
if err != nil {
slog.Warn("could not parse response body", "error", err)
}
resp := &navigation.Response{
Body: string(body),
StatusCode: httpresp.StatusCode,
Headers: utils.FlattenHeaders(httpresp.Header),
Raw: string(rawBytesResponse),
ContentLength: httpresp.ContentLength,
Resp: httpresp,
Reader: doc,
}
if b.launcher.opts.RequestCallback != nil {
b.launcher.opts.RequestCallback(&output.Result{
Timestamp: time.Now(),
Request: &req,
Response: resp,
})
}
},
)()
return nil
}
func fetchContinueRequest(page *rod.Page, e *proto.FetchRequestPaused) error {
return proto.FetchContinueRequest{
RequestID: e.RequestID,
}.Call(page)
}
// fetchGetResponseBody get request body.
func fetchGetResponseBody(page *rod.Page, e *proto.FetchRequestPaused) ([]byte, error) {
m := proto.FetchGetResponseBody{
RequestID: e.RequestID,
}
r, err := m.Call(page)
if err != nil {
return nil, err
}
if !r.Base64Encoded {
return []byte(r.Body), nil
}
bs, err := base64.StdEncoding.DecodeString(r.Body)
if err != nil {
return nil, err
}
return bs, nil
}
func netHTTPRequestFromProto(e *proto.NetworkRequest) (*http.Request, error) {
req, err := http.NewRequest(e.Method, e.URL, nil)
if err != nil {
return nil, errors.Wrap(err, "could not create new request")
}
for k, v := range e.Headers {
req.Header.Set(k, v.Str())
}
if e.PostData != "" {
req.Body = io.NopCloser(strings.NewReader(e.PostData))
req.ContentLength = int64(len(e.PostData))
}
return req, nil
}
func netHTTPResponseFromProto(e *proto.FetchRequestPaused, body []byte) *http.Response {
httpresp := &http.Response{
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
StatusCode: *e.ResponseStatusCode,
Status: e.ResponseStatusText,
Body: io.NopCloser(bytes.NewReader(body)),
ContentLength: int64(len(body)),
}
for _, header := range e.ResponseHeaders {
httpresp.Header.Set(header.Name, header.Value)
}
return httpresp
}
func (l *Launcher) PutBrowserToPool(browser *BrowserPage) {
// Discard pages that hit a deadline or were cancelled to avoid immediately
// returning a poisoned page that will fail every subsequent call.
if cerr := browser.Page.GetContext().Err(); cerr != nil {
browser.cancel()
browser.CloseBrowserPage()
return
}
// If the browser is not connected, close it
if !isBrowserConnected(browser.Browser) {
browser.cancel()
browser.CloseBrowserPage()
return
}
pages, err := browser.Browser.Pages()
if err != nil {
browser.cancel()
browser.CloseBrowserPage()
return
}
currentPageID := browser.TargetID
for _, page := range pages {
if page.TargetID != currentPageID {
_ = page.Close()
}
}
l.browserPool.Put(browser)
}
func isBrowserConnected(browser *rod.Browser) bool {
getVersionResult, err := proto.BrowserGetVersion{}.Call(browser)
if err != nil {
return false
}
if getVersionResult == nil || getVersionResult.Product == "" {
return false
}
return true
}
func (b *BrowserPage) CloseBrowserPage() {
_ = b.Close()
_ = b.Browser.Close()
if b.userDataDir != "" {
_ = os.RemoveAll(b.userDataDir)
}
}
// taken from playwright
var headlessFlags = []string{
"--disable-field-trial-config", // https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md
"--disable-background-networking",
"--enable-features=NetworkService,NetworkServiceInProcess",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-back-forward-cache", // Avoids surprises like main request not being intercepted during page.goBack().
"--disable-breakpad",
"--disable-client-side-phishing-detection",
"--disable-component-extensions-with-background-pages",
"--disable-component-update", // Avoids unneeded network activity after startup.
"--no-default-browser-check",
"--disable-default-apps",
"--disable-dev-shm-usage",
"--disable-extensions",
// AvoidUnnecessaryBeforeUnloadCheckSync - https://github.com/microsoft/playwright/issues/14047
// Translate - https://github.com/microsoft/playwright/issues/16126
// HttpsUpgrades - https://github.com/microsoft/playwright/pull/27605
// PaintHolding - https://github.com/microsoft/playwright/issues/28023
"--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades,PaintHolding",
"--allow-pre-commit-input",
"--disable-hang-monitor",
"--disable-popup-blocking",
"--disable-prompt-on-repost",
"--disable-renderer-backgrounding",
"--force-color-profile=srgb",
"--metrics-recording-only",
"--no-first-run",
"--enable-automation",
"--password-store=basic",
"--use-mock-keychain",
// See https://chromium-review.googlesource.com/c/chromium/src/+/2436773
"--no-service-autorun",
"--export-tagged-pdf",
// https://chromium-review.googlesource.com/c/chromium/src/+/4853540
"--disable-search-engine-choice-screen",
// https://issues.chromium.org/41491762
"--unsafely-disable-devtools-self-xss-warnings",
}
================================================
FILE: pkg/engine/headless/browser/cookie/cookie.go
================================================
// Package cookie implements cookie consent handling for the
// crawler. Its a partial port of I-Still-Dont-Care-About-Cookies.
package cookie
import (
_ "embed"
"encoding/json"
"sort"
"strings"
"github.com/go-rod/rod/lib/proto"
)
type CookieConsentBlockRequest struct {
ID int `json:"id"`
Priority int `json:"priority"`
Condition Condition `json:"condition,omitempty"`
}
type Action struct {
Type string `json:"type"`
}
type Condition struct {
URLFilter string `json:"urlFilter"`
ResourceTypes []string `json:"resourceTypes"`
InitiatorDomains []string `json:"initiatorDomains"`
ExcludedInitiatorDomains []string `json:"excludedInitiatorDomains"`
}
//go:embed rules.json
var rules []byte
var cookieConsentBlockRequests []CookieConsentBlockRequest
func init() {
err := json.Unmarshal(rules, &cookieConsentBlockRequests)
if err != nil {
panic(err)
}
sort.SliceStable(cookieConsentBlockRequests, func(i, j int) bool {
return cookieConsentBlockRequests[i].Priority > cookieConsentBlockRequests[j].Priority
})
}
// ShouldBlockRequest determines if a request should be blocked based on cookie consent rules
func ShouldBlockRequest(url string, resourceType proto.NetworkResourceType, initiatorDomain string) bool {
resourceTypeStr := getResourceType(resourceType)
for _, rule := range cookieConsentBlockRequests {
if matchesRule(rule, url, resourceTypeStr, initiatorDomain) {
return true
}
}
return false
}
// matchesRule checks if a request matches a specific cookie consent block rule
func matchesRule(rule CookieConsentBlockRequest, url string, resourceType string, initiatorDomain string) bool {
if !strings.Contains(url, rule.Condition.URLFilter) {
return false
}
if len(rule.Condition.ResourceTypes) > 0 {
matched := false
for _, rt := range rule.Condition.ResourceTypes {
if rt == resourceType {
matched = true
break
}
}
if !matched {
return false
}
}
if len(rule.Condition.InitiatorDomains) > 0 {
matched := false
for _, domain := range rule.Condition.InitiatorDomains {
if strings.Contains(initiatorDomain, domain) {
matched = true
break
}
}
if !matched {
return false
}
}
if len(rule.Condition.ExcludedInitiatorDomains) > 0 {
for _, domain := range rule.Condition.ExcludedInitiatorDomains {
if strings.Contains(initiatorDomain, domain) {
return false
}
}
}
return true
}
func getResourceType(resourceType proto.NetworkResourceType) string {
switch resourceType {
case proto.NetworkResourceTypeStylesheet:
return "stylesheet"
case proto.NetworkResourceTypeScript:
return "script"
case proto.NetworkResourceTypeImage:
return "image"
case proto.NetworkResourceTypeFont:
return "font"
case proto.NetworkResourceTypeXHR:
return "xmlhttprequest"
case proto.NetworkResourceTypePing:
return "ping"
case proto.NetworkResourceTypeCSPViolationReport:
return "csp_report"
case proto.NetworkResourceTypeMedia:
return "media"
case proto.NetworkResourceTypeWebSocket:
return "websocket"
case proto.NetworkResourceTypeOther:
return "other"
}
return string(resourceType)
}
================================================
FILE: pkg/engine/headless/browser/cookie/cookie_test.go
================================================
package cookie
import (
"testing"
"github.com/go-rod/rod/lib/proto"
)
func TestShouldBlockRequest(t *testing.T) {
tests := []struct {
name string
url string
resourceType proto.NetworkResourceType
initiatorDomain string
want bool
}{
{
name: "should block trustarc consent notice",
url: "https://consent.trustarc.com/notice?domain=hackerone.com",
resourceType: proto.NetworkResourceTypeScript,
initiatorDomain: "hackerone.com",
want: true,
},
{
name: "should not block trustarc for excluded domain",
url: "https://consent.trustarc.com/notice",
resourceType: proto.NetworkResourceTypeScript,
initiatorDomain: "forbes.com",
want: false,
},
{
name: "should not block non-matching URL",
url: "https://example.com/other-path",
resourceType: proto.NetworkResourceTypeScript,
initiatorDomain: "hackerone.com",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ShouldBlockRequest(tt.url, tt.resourceType, tt.initiatorDomain)
if got != tt.want {
t.Errorf("ShouldBlockRequest() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: pkg/engine/headless/browser/cookie/rules.json
================================================
[
{
"id": 1,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/iubenda_cs",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"radiomontecarlo.net",
"video.repubblica.it",
"video.lastampa.it",
"nablawave.com",
"buondi.it",
"tgcom24.mediaset.it",
"mediasetinfinity.mediaset.it",
"mediaset.it",
"telerama.fr",
"skuola.net"
]
}
},
{
"id": 2,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccm19_",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["ccm19.de"]
}
},
{
"id": 3,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "static.clickskeks.at",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 4,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/amp-user-notification-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 5,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sn.sanoma.fi/js/sccm",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 6,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/api/rest/settings/public?fields=endUserAgreement",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 7,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "congstar-media.de/fileadmin/cpolicy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 8,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "connect.danone.es",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 9,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/components/es6/PrivacyPolicy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 10,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "codice.shinystat.it/cgi-bin/getcod.cgi",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 11,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "whitepress.pl/common/pltk/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 12,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "js.hs-analytics.net/analytics",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 13,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jsdelivr.net/wp/wp-slimstat",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 14,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/dsvgo.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 15,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Flurrybox_EnhancedPrivacy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 16,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "static.axept.io/sdk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 17,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tarteaucitron.css",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 18,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "notice.sp-prod.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 19,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "htmedia.in/analytics-js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 20,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "campact.containers.piwik.pro",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 21,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "containers.piwik.pro",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 22,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wpcc.io/lib",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 23,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tag.goadopt.io",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 24,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/supi/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 25,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "omnigrafitalia.it/policy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 26,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/edgecastcdn.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 27,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/koriolis_gtm/theshield",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 28,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "fourmizz.fr/rgpd",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 29,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "hu-manity.co/hu-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 30,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ConsentManager,Sticky",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 31,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/rgpd/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 32,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/module-rgpd/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 33,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rcs_cpmt/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["video.corriere.it", "video.gazzetta.it"]
}
},
{
"id": 34,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bloc/django/ckcsfrg",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 35,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ccm19.vucx.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 36,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/avia-bootstrap/js/klaro/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 37,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "exm-medien.de/cman",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 38,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "e2d-cms.de/cman",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 39,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/usercentrics-sdk/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 40,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cc.dalten.cz/ccJs",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 41,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccm19os/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 42,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "transcend.io/cm/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 43,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/avisopcdidomi",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 44,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccm19/public/app",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 45,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "webcache.datareporter.eu",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 46,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "webcache-eu.datareporter.eu",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 47,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.realpeoplemedia.co.uk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 48,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/assets-usercentrics/uc-version",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 49,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Sticky2,ConsentManager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 50,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cnilCookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 51,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "basketballbelieve.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 52,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "app.usercentrics.eu/browser-ui/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["kicker.de", "kicker.ch"]
}
},
{
"id": 53,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cc.anytrack.de/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 54,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag/ctm/business-insurance/prod/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 55,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/GDPRPanelComponent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 56,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "aida.de/assets/coobann/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 57,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ModalEngage,ConsentManager,Sticky",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 58,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/coolkies-walkies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 59,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tagcommander/tc_",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 60,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/media/plg_system_cookieconfirm",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 61,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie_meldung.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 62,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiewarning.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 63,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiewarning4.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 64,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "catalogocoop.it/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 65,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/mu-plugins/cookie_notifier",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 66,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "freegamehosting.eu/js/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 67,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jp2w.pl/a/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 68,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "https://cdn.serviceform.com/serviceform-tools/privacy/sf-privacy-partner.js?v=nethit",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["https://www.lumise.se/"]
}
},
{
"id": 69,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"talktalk.co.uk",
"blackboard.com",
"kayak.pl",
"gamersgate.com"
]
}
},
{
"id": 70,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/inc/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 71,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "metpartner.pl/cookie/info_cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 72,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "catalogo.pusc.it/pusc_jquery.cookie.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 73,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie_law.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 74,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "lebendiges-aachen.de/includes/javascript/cookiechoices/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 75,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.reedbusiness.nl/script/cookiechecker/cookiejs.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 76,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/javascript/garante-privacy/js-cookie-master",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 77,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/cookie-compliance",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 78,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.webgenerator.nl/_NoCDN/Javascript/CookieBar/cookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 79,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiesamtykke.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 80,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/mod_pescookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 81,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiesdirective",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 82,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/webapps/bb-cookie-disclosure",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 83,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-use-policy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 84,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.orangegames.com/assets/js/cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 85,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-policy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["newpharma.be"]
}
},
{
"id": 86,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ico_cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 87,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/comun/cookie_comercial.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 88,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "hogarutil.com/js/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 89,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookielaw.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 90,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bootstrap-cookie-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 91,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieDisclaimer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 92,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/public/scripts/cookie/popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 93,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gartneriraadgivningen.dk/skinCss/website/js/cookie/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 94,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"blackboard.com",
"kayak.pl",
"portal.easygreenenergy.at",
"oekostrom.at",
"gin-rummy-online.com",
"gamersgate.com",
"spielemax.de",
"tonershop.at",
"plus-gp-joule.de"
]
}
},
{
"id": 95,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsentpopup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 96,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiePolicyEUPopin",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 97,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookielawscript.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 98,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiepolicy-client.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 99,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-disclaimer.lemm.de/cd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 100,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiebar-init.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 101,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie_consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["check24.de"]
}
},
{
"id": 102,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiewarning2.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 103,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "phufart.pl/info_o_cookie_utf8.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 104,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieInfo.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 105,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "oleificiozucchi.it/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 106,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieAccept.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 107,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "videoland.com/external/cookiewall",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 108,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiesDirective.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 109,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-law-plugin_en.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 110,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/libernazione-utils/js/libcookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 111,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "aegon.nl/data/cookiewall",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 112,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cultizm.com/templates/c2012light/javascript/cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 113,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieprivacygenerator.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 114,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogcindario.com/cookie.php",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 115,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "promoracing.it/cookies/popup.cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 116,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "harazd.net/info_o_cookie_utf8.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 117,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie_acceptance_modal.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 118,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "eurail_responsive_law_cookie_banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 119,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dometic/dist/components/cookiescomponent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 120,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "basiszinssatz.info/cookiescript.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 121,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "leporc.com/sites/all/modules/custom/info_cookies/info_cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 122,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "static.origos.hu/s/js/custom/origo/accept-cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 123,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jquery-easy-eu-cookie-law.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 124,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "shop.szakalmetal.hu//js/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 125,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.nwmgroups.hu/s/js/custom/origo/accept-cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 126,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bootsticks.npage.de/assets/js/cookieconsent.latest.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 127,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.it/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 128,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.at/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 129,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.es/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 130,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.ee/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 131,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.pl/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 132,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.cz/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 133,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.dk/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 134,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.ie/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 135,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.fr/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 136,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.si/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 137,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.hu/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 138,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.sk/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 139,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.se/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 140,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.fi/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 141,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.lt/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 142,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.gr/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 143,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.ro/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 144,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.bg/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 145,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.be/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 146,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.hr/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 147,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.de/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 148,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.pt/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 149,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.nl/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 150,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.no/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 151,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.is/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 152,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.cl/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 153,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.lv/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 154,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.ch/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 155,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.ba/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 156,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.lk/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 157,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.ru/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 158,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.com/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 159,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.co.uk/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 160,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "blogspot.ca/js/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 161,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ext/zt_popupcookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 162,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "iti.si/deljene_datoteke/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 163,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/responsive-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 164,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "skm.warszawa.pl/js/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 165,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cookie-info.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 166,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_law/plugin.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 167,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jslibrary/cookiewarning?",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 168,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/cookie-notice",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["kapitalkontor.com", "barzahlen.de"]
}
},
{
"id": 169,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "airnewzealand.co.nz/vbook/actions/cookieconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 170,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cg_cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 171,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "grsuk.com/scripts/cookie-info.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 172,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-consent.es5.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 173,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cookielaw_mip.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 174,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "informacja_cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 175,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiefix.dynamicline.hu/fixcookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 176,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 177,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-policy.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 178,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/eu-cookie-law-wp-cookie-law/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 179,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "eurocookie.galilcloud.wixapps.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 180,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-terms.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 181,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cookieStatement.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 182,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wawona.hu/cookie-box/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 183,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/components/cookiescomponent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 184,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiesinfo/cookieinfo.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 185,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent/cookie-consent.umd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 186,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiebanner.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"huk24.de",
"huk.de",
"adtipp.de",
"nectar.com",
"neckermann.de",
"exali.de",
"raiplaysound.it",
"check24.de"
]
}
},
{
"id": 187,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/common/cpol/cookiepolicy.php",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 188,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-widget/latest/cookiewidget.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 189,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "static.talparadio.nl/js/cookiecheck.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 190,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bmedonline.es/js/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 191,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consentcookie.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 192,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie.consent.is",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 193,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-notification.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 194,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "filer_-_cookie_disclaimer_ny/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 195,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cpresources/cookieconsent/js/cookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 196,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "layer22.net/scripts/cookies-no-conflict-naxa",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 197,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiecuttr-eu-cookie-law-compliance/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 198,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jquery.eu-cookie-consent.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 199,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiebar.jquery.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 200,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "trashmail.com/js/cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 201,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gratiz.nl/cookie-script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 202,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-service/js/client.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 203,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/euc_cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 204,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/web/components/cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 205,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.cookiesdirective.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["servizionline.gruppoascopiave.it"]
}
},
{
"id": 206,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-consent.azureedge.net/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 207,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jquery.cookie.policy.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 208,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/prettycookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 209,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/accept-ad-targeting/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 210,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "api.cookielaw.eu",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 211,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "api.cookielaw-script.it",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 212,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiealert.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["viessmann.com.pl"]
}
},
{
"id": 213,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/external/ico.cookie.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 214,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/script/ico.cookie.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 215,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiewall.es5.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 216,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiecompliance.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 217,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/remote/v1/cookie-notification",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 218,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-warning.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 219,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookienotice.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["betterworldbooks.com"]
}
},
{
"id": 220,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "private.dmscookie.com/scripts/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 221,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tgory.sr.gov.pl/js/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 222,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eu-cookie-law.js?browserId",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 223,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.innershed.co.uk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 224,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/mu-plugins/cookie_notifier/cn.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 225,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiebar_tls.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 226,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/smart-cookie-kit/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 227,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-notice.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 228,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookiePolicy/resources/scripts/cookie.policy.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 229,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/cookieconsent/js/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 230,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jquery.cookiepol.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 231,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-bar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 232,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie/cc_min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 233,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookielaw/piskotkar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 234,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "myportalcms.com/template/js/mp_acookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 235,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieplugin/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 236,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-message.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 237,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 238,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "doitonlinemedia.nl/global-js/avg-cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["palazzogroep.nl"]
}
},
{
"id": 239,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy.digimedia.com/check_cookie_country_code.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 240,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fortune_cookie_popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 241,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiecsnt2/js/cookie.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 242,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-note.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 243,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "policy.app.cookieinformation.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"kartor.eniro.se",
"kart.gulesider.no",
"kort.degulesider.dk",
"map.krak.dk",
"altibox.no",
"babysam.dk",
"elkjop.no",
"minaftale.dk",
"skousen.dk",
"skousen.no",
"whiteaway.com",
"whiteaway.no",
"whiteaway.se",
"dvdoo.dk"
]
}
},
{
"id": 244,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookienotice/Js/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 245,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/fortune-cookie-consent-policy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 246,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdl-cookiebar.tnt-digital.com/js/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 247,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/nl-cookie-law.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 248,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieaccept.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 249,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ercookiebar.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 250,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiewarn.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 251,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-hinweis/script-v2.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 252,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cookieMessage.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 253,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/assets/js/sera/web/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 254,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiebanner.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 255,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/z7_cookiemanager.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 256,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent/js/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["kawalekkodu.pl"]
}
},
{
"id": 257,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieconsentnotice.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 258,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/tnzcookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 259,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/gdpr-cookie-compliance",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 260,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/components/cookie-modal/cookie-modal.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 261,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-window.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 262,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiewall-inline-for-popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 263,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/cc-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 264,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiePolicyV4.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 265,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/cookiechoices.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 266,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-widget/bootstrap.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 267,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.tipsy.cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 268,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie_choices.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 269,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/wp-cookie-allow/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["learndutch.org"]
}
},
{
"id": 270,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/cookiemedia/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 271,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Resources/Public/js/cookiebar.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 272,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/plugins/system/cookiespolicynotificationbar",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 273,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieassistant.com/widget.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 274,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/helper-scripts/cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 275,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/msifestiwal/Scripts/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 276,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-overlay-pl.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 277,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.notify.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 278,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cpol/cookiepolicy.php",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 279,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-manager.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 280,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "dtt.taleo.net/careersection/theme/381362/1547022019000/en/theme/js/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 281,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/cookies/cookiesLayer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 282,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookiePolicyGa/cookiepolicyga.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 283,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sdc/cookie_consent.html",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 284,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tmo_cookies/Resources/Public/Javascript/CookieSettings.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 285,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "deltaxmultimedia.com/cookielaw",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 286,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/web/components/cookieusage",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 287,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/eagerly-tools-cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 288,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/scripts/cookiebar",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 289,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/resources/CookieConsent/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 290,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "buro-3.nl/cookie/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 291,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ll_cookie_bar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 292,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "api.cookiemonster.is/embed",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 293,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/g361-cookies-consents",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 294,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Scripts/Cookies/cookieacceptance.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 295,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/extimages/scripts/ukcookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 296,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.aptelink.pl/nc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 297,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "eu_cookie_banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 298,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eu_cookie_compliance.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 299,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dc-cookie-privacy-settings.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 300,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiesPolicy/static/lib.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 301,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiewarning-nosql.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 302,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.bpcookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 303,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/resources/js/cookie/cookie-bar",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 304,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieConsentDialog.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 305,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/bc-cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 306,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/idxcookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 307,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/javascript/cookieser.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 308,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiesWidget/widget.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 309,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.cookiebot.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"storyhouseegmont.dk",
"storyhouseegmont.no",
"storyhouseegmont.se",
"egmont.com",
"stadtmobil.de",
"wwf.fi",
"login.nos.pt",
"asambeauty.com",
"cineplex.de",
"berlingske.dk",
"digimon.kochfilms.de",
"kino.dk",
"hoyavision.com",
"linak.es",
"linak.de",
"cajamar.es",
"digitaltrends.com",
"mein-grundeinkommen.de",
"werder.de",
"finanzmarktwelt.de",
"danbolig.dk",
"bt.dk",
"scubadiving.com",
"biomarkt.de",
"harzwasserwerke.de",
"stern.de",
"dasinvestment.com",
"derivate.bnpparibas.com",
"werkenbijlidl.nl",
"swspremberg.de",
"nngroup.com",
"bankia.es",
"bergbauernmilch.de",
"spiele-kostenlos-online.de",
"ekstrabladet.dk",
"epochtimes.de"
]
}
},
{
"id": 310,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccp-sites/components/structure/cookie-notification/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 311,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-info.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 312,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.algo.at",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 313,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiehub.net/c",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 314,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/widgets/eu-cookie-law/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 315,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "app.cookieyes.com/client_data",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 316,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/skinCss/website/js/cookie/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 317,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "freeprivacypolicy.com/cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 318,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-module/dist",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 319,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieOptIn.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 320,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cookie_banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 321,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery-cookies-alert.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 322,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookielaw.emea.fcagroup.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 323,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/tnk-cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 324,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiepopup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 325,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.cookiefirst.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"swietawdomu.pl",
"deutschesapothekenportal.de"
]
}
},
{
"id": 326,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-policy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 327,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/liquorice-cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 328,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-switch.viminds.de/api",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 329,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "skyscnr.com/sttc/oc-registry/components/cookie-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 330,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/cookiesplus/views/js/cookiesplus.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 331,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mws-cookie-solution.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 332,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ajax/libs/cookieconsent2",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"membersuite.com",
"la-becanerie.com",
"rijbewijskeuringennederland.nl",
"download.pixelexperience.org",
"ceramtec-group.com"
]
}
},
{
"id": 333,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"talktalk.co.uk",
"codexis.cz",
"lacoste.com",
"huuray.se",
"indiearenabooth.com"
]
}
},
{
"id": 334,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookienotice-bootstrap.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 335,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/cookieEuGH",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 336,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiebar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 337,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/cookie-notice",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 338,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiechoices.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 339,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 340,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieconsent.popupsmart.com/src/js/popper.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 341,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "flixbus.com/cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 342,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/jscookieconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 343,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.cookie-policy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 344,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-alert.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 345,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "up-cookiemon.wavecdn.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 346,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieBanner.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"adtipp.de",
"nectar.com",
"neckermann.de",
"exali.de",
"raiplaysound.it",
"check24.de"
]
}
},
{
"id": 347,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/politica-cookies/leyCookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 348,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/trwcookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 349,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dc_components/site/cookie-control/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 350,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-manager.de/cookie-manager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 351,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pxpcookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 352,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie/uccb-main",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 353,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_consent_min_js.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 354,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/onlinemanufaktur-cookie-notice/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 355,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mq_cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 356,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/static/cookies/cookie-layer-wc-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 357,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-bar/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["vectra.pl", "urbanista.de"]
}
},
{
"id": 358,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiebar/scripts.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 359,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fileadmin/cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 360,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieopt-min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 361,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/micado.web.dsgvo.cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 362,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wip.pl/js/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 363,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/docroot/js/app/modules/cookies-selection.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 364,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/cookieman/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 365,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiecode.dist.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 366,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_agreement_dialogue.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 367,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "googleapis.com/55_cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 368,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/nsd-cookie-banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 369,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/responsive-eu-cookie-notice",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 370,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/master-popups-cookieplus",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 371,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-banner-one.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 372,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiemanagement.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 373,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/mod_eu_cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 374,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.cookiefy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 375,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "websitepolicies.com/lib/cookieconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 376,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/aip_cookie_law",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 377,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie.gg/c",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 378,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/plg_system_vpcookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 379,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/p8-cookie-bar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 380,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-banner/cb.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 381,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/echonetcookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 382,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-policy-en.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 383,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bb_cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 384,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/?tx_z7cookiemanager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 385,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/kmacookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 386,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/_hw_cookie_dialog.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 387,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-settings-ui/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 388,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookietrust.eu/script",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 389,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bs-cookie.staging.springbok.agency",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 390,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-banner.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["nice.org.uk"]
}
},
{
"id": 391,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/WebUI/Cookies/allowcookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 392,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/SharedComponents/bundle-scripts/cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 393,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent.klaro.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 394,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cookies_bar",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 395,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modal/cookie-wall/?modal_view=true",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 396,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/better_cookie_consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 397,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/jquery-eu-cookie-law-popup",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 398,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "flipdish-cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 399,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cookie-law-ansi",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 400,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieconsent.syreta.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 401,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/deferred/cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 402,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/rodo-cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 403,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/hw-cookie-dialog.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 404,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/accept-cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 405,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_warning.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 406,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/plugins/cookies/tinybox.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 407,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/web/components/cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 408,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-bar.salessquad.co.uk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 409,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieMunchr.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 410,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "nicmanager.com/static/cookie_guideline",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 411,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "traderfox.de/lib/tfcookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 412,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/amadeus-plugin-cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 413,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rh-cookieconsent-microsites.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 414,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/components/content/cookie-overlay/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 415,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiemon.atcom.gr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 416,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "nbcsports.com/cookie-ack.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 417,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/gdpr/gdpr-cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 418,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.ihavecookies.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["wisla-plock.pl"]
}
},
{
"id": 419,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/b2b-market/src/addons/cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 420,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/vue/components/cookie-monster",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 421,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/do-you-want-cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 422,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bascom.nl/cookies/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 423,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fluvius-eu-cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 424,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/lyxor-cookies-disclaimer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 425,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/grt-cookie-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 426,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bundles/cookiebar",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 427,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/avia-snippet-cookieconsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 428,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/user-cookie-banner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 429,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/we_cookie_consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 430,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/vp-cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 431,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/system/modules/cookiecontrol/assets/js/cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 432,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieConsentNotif.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 433,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/ne_cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 434,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-bar.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 435,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ilovecookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 436,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/luxcookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 437,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiehint/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 438,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieOptIn.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 439,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sg_cookie_optin/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 440,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiebox.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["footroll.pl"]
}
},
{
"id": 441,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.privacy.protection.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 442,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/new-cookie-policy.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 443,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiehint.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 444,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/public-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 445,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mod_jt_cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 446,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/dist/cookieAcceptance",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 447,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiebanner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 448,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/acc.cookienotification.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 449,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiepolicy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 450,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie_api/cccframe",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 451,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookienote-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 452,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/metro?sc_device=webcomponent&components=cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 453,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_msg.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 454,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_info.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 455,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "js.cookietagmanager.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 456,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/assets/mnd-cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 457,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js_cookies_legal.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 458,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ys_cookie_consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 459,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-v2/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 460,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/beautiful-and-responsive-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 461,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie/banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 462,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/widget-module-cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 463,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/lightweight-cookie-notice/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 464,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ocean-cookie-notice/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 465,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jst_eu_cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 466,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "procookie.by.nf",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 467,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 468,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jquery-eu-cookie-law-popup",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 469,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cp_cookieconsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 470,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.ae-webdesign.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 471,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ws5_eucookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 472,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieConsent/cookieModal.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 473,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/consent-cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 474,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.fo/qookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 475,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/hw-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 476,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["labs.strava.com", "crossfit.com"]
}
},
{
"id": 477,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/w4.cookiebar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 478,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/cookielay/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 479,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "plugin-cookie-consent/build",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 480,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie/onetrust/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 481,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-settings-modal/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 482,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/components/consent-cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 483,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-service.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 484,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent.bundle.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 485,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/lgpd-cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 486,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/cookies-and-content-security-policy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 487,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiebox_uc_build.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 488,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gen/cookie-notification",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 489,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiesjsr.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["lcp.fr"]
}
},
{
"id": 490,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/eu-cookies-bar/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 491,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieOverlay-view.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 492,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/Widgets/cookieWidget",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["losteria.de"]
}
},
{
"id": 493,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/minified/cookiecontrol.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 494,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/ab1d_cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 495,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/module/kabimba_cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 496,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/woody-addon-cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 497,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/__enzuzo-cookiebar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 498,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_policy/ack.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 499,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["northernpowergrid.com"]
}
},
{
"id": 500,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/zedwcookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 501,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiedisclosure/core.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 502,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie.thynk.media/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 503,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/usercentrics/cookiebox",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 504,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-tool/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 505,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-notification.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 506,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "onetrust.com/cookieconsentpub",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["fujifilm-x.com"]
}
},
{
"id": 507,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/dp_cookieconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 508,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 509,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/om_cookie_main.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 510,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.rgpd-cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 511,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bandeau_cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 512,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dist/js/cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 513,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"sonnenverlauf.de",
"mondverlauf.de",
"leanlibrary.com",
"retrogames.cc",
"exagon.de",
"sozialversicherung-kompetent.de",
"varcevanje-energije.si",
"khl.ru",
"bauen-und-heimwerken.de",
"carport-diagnose.de",
"tricount.com"
]
}
},
{
"id": 514,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieConsentApp.js.gz",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 515,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fileadmin/templates/cookie/fc_thin.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 516,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.cookiecode.nl",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 517,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/nd-cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 518,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent-all.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 519,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieBanner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 520,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-wall.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 521,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.giant.cz/assets/consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 522,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.praguebest.cz/dist",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 523,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-prompt/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["gestiontv.vodafone.es"]
}
},
{
"id": 524,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bootstrap-cookie-consent-settings.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 525,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rails_cookie_consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 526,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rgpd/js/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 527,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieBannerLoader.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 528,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieform.pl/assets/js/plugin",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 529,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/assets/components/cookiemanager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 530,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ewcookienoteext.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 531,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/clientlibs/components/content/cookie/cookielayer",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 532,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rm-cookieconsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 533,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-banner-vue",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 534,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiecdn.com/cwc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 535,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/om-gdpr-cookie-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 536,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn-cookieyes.com/client_data",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 537,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "digitalsternemarketing.de/dscookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 538,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modal-acceptare-cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 539,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-bar/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 540,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookieconsent.blob.core.windows.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 541,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/all-cookieconsent-js.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 542,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-browser-alert-ui.fragment.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 543,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tcf/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 544,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bsgdprcookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 545,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": ".cookiehub.eu",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 546,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "traveljuice-engines.prod.traveljuice.fr/cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 547,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eu_cookie_compliance_override.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 548,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/smart-eu-cookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 549,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 550,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Pictorium/scripts/cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 551,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jsLib/gdpr/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 552,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieBar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 553,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cc.controlcookies.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 554,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieNotice.build.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 555,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ux.ui-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 556,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiesPopUp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 557,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/in2cookiemodal",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 558,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-banner/cookie-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 559,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/polityka-prywatnosci/js/cookie-overlay",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 560,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-portlet/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 561,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/simple-gdpr-cookie-compliance/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 562,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-consents.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["posta.sk"]
}
},
{
"id": 563,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "theme-cookie/app/cookie.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 564,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "plugins/ShprCookieFavour/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 565,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.ptj.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 566,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieModal/clientlib",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 567,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-banner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 568,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dxcookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 569,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/real-cookie-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 570,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sohoshopcookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 571,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "am-static.com/cookie-banner/sdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 572,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/marked-cookie-consent-web/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 573,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.cookielaw.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["cnn.com"]
}
},
{
"id": 574,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/borlabs-cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 575,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/gdpr-cookie-compliance/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 576,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/etagen_cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 577,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_overlay.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 578,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/plugins/cookies-and-content-security-policy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 579,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieBar.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 580,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiedialog/cookieutility.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 581,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/creare-eu-cookie-law-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 582,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jsdelivr.net/npm/@finsweet/cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 583,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieseubox.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 584,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie/cc_cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 585,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieConsent.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["fransdewitte.nl"]
}
},
{
"id": 586,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/category-assets/experiences/recurring/cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 587,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/storage/ui/cookies-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 588,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiedisturber/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 589,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/lightweight-cookie-notice",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 590,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/dm_cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 591,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-dialogue.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 592,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dist/scripts/cookie-control",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 593,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "am-static.com/cookie-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 594,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/marked-cookie-consent-web/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 595,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "app.falconcookie.de/storage",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 596,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/brd_cookies_consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 597,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/hicookielaw/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 598,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/kbv-cookieconsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 599,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-mycity.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 600,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/components/src/cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 601,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/webtoffee-gdpr-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["altheaprovence.com"]
}
},
{
"id": 602,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.neuca24.pl/nc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 603,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies/manage-cookies-runtime",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 604,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/ppw_cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 605,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-widget/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 606,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/pages/js/cookie-dialog.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 607,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3temp/assets/compressed/cookieman",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 608,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiethough.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 609,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "beyond-cookiebanner.de/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 610,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/assets/js/cookie_consent_manager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 611,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/viva-cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 612,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieMessage.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 613,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/michnhokn/cookie-banner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 614,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ddjnocookie.com/prod_public",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 615,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/cookie_policy/frontend/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 616,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 617,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdpsystem.eu/pl/cookiesprivacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 618,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fcc-cookie-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 619,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-cookie-consent.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 620,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieConsentScript",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 621,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookienoticepro.script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 622,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dsgvo/js/cookiemonster",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 623,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_consent_js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 624,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent-request.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 625,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wko.at/gcm/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 626,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wko.at/css-js/scripts/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 627,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/frontend-core/js/cookieBox.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 628,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiemanager.dk/js/cm.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 629,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mod_pixim_cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 630,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tmgonlinemedia.nl/consent/script/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 631,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy.ariadneathome.nl/script/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 632,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy.vtwonen.nl/script/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 633,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wpjslib-chunk-consent-form.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 634,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/consentbar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 635,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdprpro/views/js/gdpr-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 636,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/consent-banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 637,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "s.clickiocdn.com/t/consent_",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 638,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jppol-consent/js/bootstrap.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 639,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/opbox-rodo-consent-modal/index",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 640,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "optanon.blob.core.windows.net/consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 641,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "libertatea.ro/consent/config.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 642,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent-notice.magix.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 643,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/taunton-user-consent-eu",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 644,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consensu.org/t/consent_",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["softzone.es"]
}
},
{
"id": 645,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consentmanager.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"sourceforge.net",
"webfail.com",
"sudoku-aktuell.de"
]
}
},
{
"id": 646,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consently.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 647,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "s.adroll.com/j/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 648,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-panel-vue.chunk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 649,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.theneweuropean.co.uk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 650,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy-consent-banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 651,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "thezen.garden/projects/zenconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 652,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "smartconsent.ro/js/smart-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 653,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/webflow-consent-manager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 654,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "app.consentassist.com/widget.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 655,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "s.luxupcdnc.com/t/consent_",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 656,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.appconsent.io",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["lefigaro.fr"]
}
},
{
"id": 657,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.23g.io",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 658,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "browser-consent-front.coco.s-cloud.fi",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 659,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/assets/consent-manager.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 660,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.weersvoorspelling.nl/v1",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 661,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.webmasterplan.com/v2",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 662,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/adsconsent.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 663,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.truste.com/get?name=notice.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 664,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.trustarc.com/notice",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"tripit.com",
"concursolutions.com",
"fortune.com",
"formula1.com",
"forbes.com"
]
}
},
{
"id": 665,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent-manager.metomic.io",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 666,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.uniconsent.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["photopea.com"]
}
},
{
"id": 667,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-banner-bootstrap",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 668,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tmgrup.com.tr/tmd-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 669,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/consent-modal.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 670,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/api/snippets/js/consent-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 671,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sixfifty.com/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 672,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 673,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "choices.consentframework.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 674,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cache.consentframework.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 675,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consentserve.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 676,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "analytics-consent-manager.azureedge.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 677,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mgm_consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 678,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "hoffmann-group.com/common/consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 679,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dv_t3_consent_management/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 680,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gsso_consent_manager.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 681,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "avandor.com/consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 682,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/data_consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 683,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "smart.idmnet.pl/consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 684,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "kalevakonserni.fi/consent/gravito",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 685,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bundle-consent-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 686,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modal-consent-component.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 687,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-management/_include/init_consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 688,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "pitcom-webanalyse.de/dsgvo/consent-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 689,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wrd-aws.com/consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 690,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/ag-consentmanager-no-bootstrap/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 691,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/static/consent-dialog.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 692,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/opbox-gdpr-consents/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 693,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/consent-manager.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 694,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ampproject.org/v0/amp-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["smartdroid.de", "lelum.pl"]
}
},
{
"id": 695,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.trustarc.com/v2/notice",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 696,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/resources/onetrust/js/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 697,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-manager/lazy-consent-manager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 698,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/addons/consent_manager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 699,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/npm/ez-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 700,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/piwik-consent-banner-script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 701,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fileadmin/template/js/cconsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 702,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/kconsent/kconsent29.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 703,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-layer/js/consent-layer-loader.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 704,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tm-gdpr-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 705,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "snigelweb.com/adconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 706,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sncmp.com/adconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 707,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consentmanager.net/delivery/js/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["winfuture.de", "infranken.de"]
}
},
{
"id": 708,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent-manager.confirmic.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 709,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consentbanner.de/public/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 710,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "classistatic.de/consent-statics",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 711,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consentcheck.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 712,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-consent-management-platform/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 713,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/drdsgvo-consent-script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 714,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-consent.bundle.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 715,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent/civic-bundle.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 716,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "totalweb.gr/gdpr/consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 717,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/epaas.consentdrawer.bundle",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 718,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consentbanner-fragment/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 719,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "altravia.com/connector/consent_mode.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 720,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consentmanager.net/delivery/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["winfuture.de", "infranken.de"]
}
},
{
"id": 721,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/taunton-user-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 722,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fileadmin/templates/js/cconsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 723,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/okf-euconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 724,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.daa.net/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 725,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-consent-banner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 726,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-management-app/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 727,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.hr/delivery/js/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 728,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bundles/consent-init?v=",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 729,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/acc.consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 730,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.scm-verlagsgruppe.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 731,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/consent-magic-pro",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 732,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "7gra.us/consentbar",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 733,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dgp-cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 734,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/assets/as24-cmp/consent-banner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 735,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/log-user-consents/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 736,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent-bist.de/public/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 737,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.digiapi.com/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 738,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "idealo.com/storage/cmp/consent-management.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 739,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.prointernet.com/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 740,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/coockieconsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 741,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consentmanager.net/delivery",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"winfuture.de",
"echo-online.de",
"stern.de",
"spar.hu",
"spar.hr",
"spar.at",
"spar.si",
"interspar.at"
]
}
},
{
"id": 742,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.comply-app.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 743,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "kesko.fi/kconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 744,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "unpkg.com/@segment/consent-manager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 745,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wko.at/static/ct/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 746,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consenttool.haendlerbund.de/app.js?apiKey",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 747,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.extrazimut.net/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 748,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cs_consent_modal.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 749,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/gdpr_footer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 750,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/gdpr.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 751,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/comun/avisopcgdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 752,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 753,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdpr.internetbrands.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 754,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": ".be/api/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 755,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": ".nl/api/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 756,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "-gdpr-min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 757,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-transparency-apnxs/latest/gdpr-bundle.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 758,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sitemaps.services.zam.com/gdpr_optout.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 759,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ibeugdpr.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 760,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consensu.org/gdpr/cmp/gdpr-cmp-ui.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 761,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "s3.amazonaws.com/sitemaps.services.zam.com/gdpr_standalone.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 762,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sizzlegdpr.snippet.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 763,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/gdpr/messaging.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 764,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mdp.javascript.gdpr.min",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 765,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/emg-framework/public/js/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 766,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/emg-framework/assets/js/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 767,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/assets/js/gdpr.js?ver",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 768,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/injectable.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 769,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ishop-plugins/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 770,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "secure-cdn.mplxtms.com/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 771,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 772,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/am2-gdpr-public.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 773,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdprscript.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 774,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/plugins/surbma-gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 775,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-banner.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 776,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "libraries.wmgartistservices.com/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 777,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdprmain/prod/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 778,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.thisisdone.com/gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 779,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdpr-banner.awsmpsa.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 780,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "securebrainpull.com/gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 781,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdpr500.com/widget/pandawidget/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 782,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bundle-gdpr.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 783,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/CookieConsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["kayak.pl"]
}
},
{
"id": 784,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/2018-gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 785,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-cmp-ui.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 786,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/gdpr.min.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 787,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/plugins/gdpr/gdpr_ncoi.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 788,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/static/new/js/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 789,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/libs/gdpr/cmp/cmp.bundle.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 790,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pgdpr.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 791,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/policies/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 792,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/spd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 793,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "prizelogic.com/gdpr/third-party-optin.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 794,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 795,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wordpress-ptchrgdprplugin/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 796,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdpr-wrapper.privacymanager.io",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"kinuskikissa.fi",
"geenstijl.nl",
"dailybuzz.nl",
"rtvnoord.nl",
"omroepbrabant.nl",
"futurezone.at",
"profil.at",
"kurier.at",
"weeronline.nl",
"lablue.de",
"vesti.bg"
]
}
},
{
"id": 797,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "evidon.com/pub/gdprnotice.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 798,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/ct-ultimate-gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 799,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdpr.mandarin-medien.de/manager.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 800,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdpr-tcfv2.sp-prod.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"computerbild.de",
"programme-tv.net",
"bold.dk",
"welt.de",
"sport1.de",
"ostsee-zeitung.de",
"sky.com",
"lvz.de",
"mein-schoener-garten.de",
"autobild.de",
"bike-bild.de",
"bz-berlin.de",
"travelbook.de",
"si.com",
"capital.fr",
"t3n.de",
"is.fi"
]
}
},
{
"id": 801,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "yastatic.net/s3/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 802,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/zgdpr.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 803,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/pp_agreement.pc.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 804,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/cmp/cmpBundle.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 805,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/webmd.gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 806,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/motus-gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 807,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/plugins/gdprprivacysetup/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 808,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc-gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 809,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "metronom.com/library/scripts/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 810,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/lin_gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 811,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tronic-gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 812,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-banner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 813,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/typo3conf/ext/dbb_gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 814,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-appliance.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 815,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "pdcc.gdpr.es",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 816,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-redirect.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 817,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/shared/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 818,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sdk-gdpr.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 819,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/gdpr-component",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 820,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdprDialog.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 821,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bluelabs-gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 822,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdprScriptComponent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 823,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "mediavine.com/tags/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 824,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "pubnation.com/tags/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 825,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ecos.am/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 826,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modal-gdpr.umd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 827,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr_notice.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 828,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/lexon-gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 829,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/popin-gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 830,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modules/gdprpro/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 831,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gdpr-api.sharethis.com/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 832,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/media/plg_system_eprivacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["reshade.me"]
}
},
{
"id": 833,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "recepttar.hu/js/privacy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 834,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.dmgmediaprivacy.co.uk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["metro.co.uk", "gbnews.uk"]
}
},
{
"id": 835,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jssdk.privacy.pre.schibsted.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 836,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "secureprivacy.ai/secureprivacy-plugin/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 837,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/vendor/weka/privacykit/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 838,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy.clym.io/js/clym-widget.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 839,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/privacypolicy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 840,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bosch-privacy-settings-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 841,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/general/avada-privacy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 842,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "public.message-business.com/Javascript/mb.privacyManager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 843,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.reeltime.no/pm_assets/privacy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 844,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ftcguardian.com/privacy-update",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 845,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacypolicy.trgr.be/widget",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 846,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacyopt.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 847,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacypolicy/styles/all/template/remove_url.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 848,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "psi.schibsted.com/api/v2/privacy/notification",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 849,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/privacy-policy-info/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 850,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/e-privacy.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 851,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacyConsentBar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 852,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tagcommander.com/privacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["labanquepostale.fr"]
}
},
{
"id": 853,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "isgprivacy.cbsi.com/dist/optanon",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"cbsnews.com",
"startrek.com",
"insideedition.com",
"cbslocal.com",
"etonline.com"
]
}
},
{
"id": 854,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/contao-privacy-center.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 855,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-policy.u-lab.nl",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 856,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "trustcommander.net/privacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["tf1info.fr", "tf1.fr"]
}
},
{
"id": 857,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "app.eprivacy-keeper.eu",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 858,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/en-privacy-notification/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 859,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy.claytonhomes.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 860,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "shopify.com/shopifycloud/privacy-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 861,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/enua-privacy-policy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 862,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "demotywatory.pl/res/js/privacy_policy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 863,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eprivacy/js/eprivacy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 864,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sf-tagomo-privacy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 865,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/s4s-privacy-module/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 866,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/shopsshort/privacy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 867,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "heg-cp.com/upm/privacy-manager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 868,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tag=ui/privacy/CookiesConsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 869,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dock-privacy-settings.esm.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 870,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy.wum.rocks/public/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 871,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tagcommander/privacy_",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 872,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/serviceform-tools/privacy/sf-privacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 873,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy-dialog-tracking.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 874,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy/providers/CookiesDataProvider",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 875,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/contao-privacy-center",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 876,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/myagileprivacy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 877,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.lemonde.fr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 878,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.quantcast.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 879,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.nextday.media/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["vi.nl", "omroepwest.nl"]
}
},
{
"id": 880,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.dreamlab.pl",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 881,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/media/cmp/int_cmp_banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 882,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consensu.org/delivery/cmp.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 883,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sipaof.mgr.consensu.org/sipacmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 884,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/sourcepoint/sp-msg.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 885,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.cdntrf.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 886,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.mediavine.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 887,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmpCookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 888,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/library/cmp/cmp.bundle-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 889,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp-loader.choice.faktor.io",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 890,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/diyscmp.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 891,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bilsyndication.com/js/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 892,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bilsyndication.com/plugins/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 893,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.osano.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 894,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "vlitag.com/plugins/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 895,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.md-nx.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 896,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.opencmp.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["foraum.de", "idowa.de"]
}
},
{
"id": 897,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.faktor.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["omroepwest.nl", "consent.talpanetwork.com"]
}
},
{
"id": 898,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gravito.net/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 899,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdncmp.richaudience.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 900,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "heymatic.com/assets/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 901,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/js/vendors~cmpUi",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 902,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ustatik.com/public/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 903,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "mrf.io/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 904,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.cls.pm",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 905,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gravito.net/lightcmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 906,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "onetag-sys.com/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 907,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/truendo_cmp.pid.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 908,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.optad360.io",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 909,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "snigelweb.com/sncmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 910,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "seznam.cz/js/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 911,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pubtech-cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 912,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.pafo.fairbung.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 913,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sibbo.net/v2/sibbo-cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 914,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "24net.cz/resources/js/cmp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 915,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/quorn-cmp.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 916,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmps.o2.cz/delivery",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 917,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tcf-cmp.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 918,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "fastcmp.com/fast-cmp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 919,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "apps.ludostation.com/cmp/v2/cmp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 920,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "mrdev-cmp/assets/js/script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 921,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.setupcmp.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 922,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.meteored.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 923,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "veodys.fr/api/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 924,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sibbo-cmp-core.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["atresplayer.com"]
}
},
{
"id": 925,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gravito.net/alehdetcmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 926,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "fastcmp.com/fast-cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 927,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sibbo-cmp-loader.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 928,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.springernature.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 929,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "determinator.service-cmp.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 930,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "seznam.cz/js/cmp2/scmp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 931,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "seznam.cz/js/cmp2/scmp-external.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 932,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gofundme.com/js/3.0/visitorCookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 933,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookiesDirective",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 934,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "CookieAccept/affirmation.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 935,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ariba.com/assets/scripts/classes/Ariba.Compliance.CookieConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 936,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "aldermore.co.uk/Scripts/Logic/CookieDisclaimer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 937,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tweeboo.com/r/js/CookieDirective",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 938,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jqueryCookieGuard",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 939,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jquery.smartCookie.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 940,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc-bar/cCookiesH.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 941,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookieDirective.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 942,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/setPrivacyCookie.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 943,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/clientlib-webpack-publish/js/CookiesApp-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 944,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/CookieManager.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 945,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bundle_CookieLegalNotice.prod.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 946,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/acceptCookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 947,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/i_CookieConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 948,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Amasty_GdprCookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"planet-cards.co.uk",
"sunkost.no",
"wondrium.com",
"nova-motors.de",
"moleonline.com",
"durstexpress.de",
"littlelunch.com",
"twinpack.nl",
"eckeroline.fi",
"eilles.de",
"xt500parts.com",
"cupper-teas.de"
]
}
},
{
"id": 949,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ts/components/CookieConsent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 950,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/SDG_CookieLayer.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 951,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/slr_js/allowCookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 952,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/javascript/component-CookieConsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 953,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/whCookieManager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 954,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Cookie-GetCookieModal",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 955,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/1990KB-CookieConsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 956,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Magento_Cookie/js/notices",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 957,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/iwCookieBanner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 958,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookieConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"blackboard.com",
"kayak.pl",
"gamersgate.com"
]
}
},
{
"id": 959,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/CookieConsentNew",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 960,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookieManagerUi.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 961,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/widgets/global/vendors~LegalCookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 962,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/getCookieConsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 963,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/kssCookieManager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 964,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ModalCookiesPrivacy.php",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 965,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookiesManager/CookiesManager.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 966,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/components/CookieManager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 967,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/SiteElements/Scripts/CookieBanner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 968,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/plugins/CookiePop/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 969,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/IntegerNet_CookieConsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 970,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/new_Cookiebanner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 971,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/feoCookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 972,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Plumrocket_CookieConsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["8020.net"]
}
},
{
"id": 973,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/static/CookieManager/js/app",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 974,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/optInCookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 975,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ja/controlCookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 976,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utils/CookiePrompter",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 977,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Wamoco_CookieConsentUi/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 978,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/standaloneModalCookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 979,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Detco_CookieBanner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 980,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/vfConsentCookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 981,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Cookie-Interfrog/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 982,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "quantcast.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"sourceforge.net",
"tumblr.com",
"wpolityce.pl",
"player.livespotting.com",
"indy100.com",
"vi.nl",
"independent.co.uk",
"express.co.uk",
"joe.ie",
"joe.co.uk",
"standard.co.uk",
"avsforum.com",
"pcgamer.com",
"nfl.com",
"filmvandaag.nl",
"gamesradar.com",
"iol.pt"
]
}
},
{
"id": 983,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "gemius.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 984,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sddan.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 985,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "digitrust.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 986,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sharethis.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 987,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "webads.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 988,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "etarget.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 989,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "optad360.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["infogliwice.pl"]
}
},
{
"id": 990,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "shinystat.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 991,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "dan.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 992,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ogury.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 993,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "inforpl.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 994,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "sibboventures.mgr.consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 995,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consensu.infor.pl",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 996,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ciasteczkowapolityka.pl/getscript",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 997,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bsl.nl/extern/smbv-incl/script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 998,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ssl.synovite-scripts.com/ut",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 999,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "zoneadsl.com/clientscript/cnil.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1000,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "angrybirdsmovie.net/site/scripts/nettracking4.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1001,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bobulous.org.uk/javascript-head-sitewide.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1002,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wroclaw.pl/portal/themes/js/script-rodo.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1003,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/scripts/pookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1004,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "metasrc.com/assets/javascripts/compliance.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1005,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "geo.fr/assets/scripts/sourcepoint.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1006,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/rodo/rodo_script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1007,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.praivacy.eu/scripts",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1008,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "script.getcomplied.com/scripts/complyWidget/assets/getCompliedListWidget.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1009,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Gpdr/assets/ccc-script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1010,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "kookiecheck.cz/static/script",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1011,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "legalblink.it/api/scripts/lb_cs.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1012,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dgsvo/script.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1013,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/cc/js/cc.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1014,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "common.i12.de/cms/file/plugin/dp/dp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1015,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/shapepress-dsgvo/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1016,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/iticonseil-rgpd/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1017,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/pixelmate/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1018,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/pixelmate-opt-in/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1019,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "static.trbo.com/plugin",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1020,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/fb-pixel-dsgvo/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1021,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "id-ward.com/static/idw_plugin",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1022,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ishop-plugins/ishop-cp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1023,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/rrze-legal",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1024,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/rrze-legal",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1025,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wp-content/plugins/jjarolim-tracking/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1026,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/acton/bn/tracker",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1027,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "etracker.de/optin_overlay.php",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1028,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/libraries/google/do-not-track.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1029,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tracking-permission-dialog.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1030,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "origin-images.wikia.com/fandom-ae-assets/tracking-opt-in",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1031,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tracking-opt-in.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1032,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/contaotrackingmanager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1033,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/peg_utils/tracking/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1034,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/kixsimpletrack/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1035,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rodo.js?pp_pr=",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1036,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/templates/rodo/rodo.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1037,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/rodo/rodo.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1038,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/rodo.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1039,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "rodo.agora.pl/agreement/check",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1040,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/min-js?f=js/rodo.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1041,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rodo_rmf",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1042,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/popup/rodo.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1043,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rodo/rodo.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1044,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rodo-agreement-popup.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1045,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "www.capitol.fr/streaming/cnil/cnil.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1046,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "prismamediadigital.com/cnil.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1047,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "newsletters.ftv-preprod.fr/cnil/js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1048,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Dock/DockContent/Cards/GDPRCard/index.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1049,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "nanoGDPR.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1050,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/GDPR/GDPR.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["3ds.com"]
}
},
{
"id": 1051,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/GDPRPanelComponent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1052,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tdn.r42tag.com/lib/ut",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1053,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "c.betrad.com/pub/third.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1054,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "fundingchoices.google.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1055,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "fundingchoicesmessages.google.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": [
"rtl.hr",
"fernsehserien.de",
"bbc.com",
"iol.pt",
"stooq.pl",
"stooq.com"
]
}
},
{
"id": 1056,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "spiffymachine.com/v2/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1057,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.userdatatrust.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1058,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.pubguru.com/pg.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1059,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "qualtrics.com/WRSiteInterceptEngine",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["logmeininc.com"]
}
},
{
"id": 1060,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "a.svtrd.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["ns.nl"]
}
},
{
"id": 1061,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "foolcdn.com/mms/resources/js/international-visitor-notice-js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1062,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "heraeus.com/media/system_files/special_applications/heraues_datapolicy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1063,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "libraries.wmgartistservices.com/pplightbox",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1064,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "assets.ubembed.com/universal",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1065,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "pi.pardot.com/analytics",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1066,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "api.useinsider.com/ins.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1067,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "a.optmnstr.com/app/js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1068,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "borderfree.com/v1/dist/cbt.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1069,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.justuno.com/mwgt",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1070,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "caringzinc.com/v2",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1071,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "dam.bbcchannels.com/m/2fmpg/js/outside-iframe.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1072,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "dam.bbcchannels.com/m/2fmph/js/outside-iframe.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1073,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.truendo.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1074,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "intelligentscissors.com/v2",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1075,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.adnuntius.com/adn.dmp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1076,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cct-pubweb.com/ccpa",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1077,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "widgets.legalmonster.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1078,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "nexus.ensighten.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1079,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "einfachonline.com/sid",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1080,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/railwayreason.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1081,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/teenytinycellar.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1082,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/lovelydrum.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1083,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/hatefulrequest.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1084,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/aloofvest.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1085,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacidade.api.milvus.com.br",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1086,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "global.ketchcdn.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1087,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "e-i.com/SITW",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1088,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pleasantpump.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1089,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fearlessfaucet.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1090,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "unpkg.com/orejime",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1091,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/superficialeyes.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1092,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cassiecloud.com/loader.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1093,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/childlikeform.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1094,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "baycloud.com/tgcl.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1095,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/chickensstation.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1096,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/handsomelyhealth.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1097,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "s2.getsitecontrol.com/widgets",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1098,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "prismamediadigital.com/cnil.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1099,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.efilli.com/efl.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1100,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "api.brookiebot.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1101,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tiqcdn.com/utag/tui/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1102,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/basketballbelieve.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1103,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/colossalchance.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1104,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/aliasanvil.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1105,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "api.byscuit.com/data/client",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1106,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.lawwwing.com/widgets",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1107,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/hallowedinvention.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1108,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "pcz.pl/static/js/moo-cooker.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1109,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eucd/eucd.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1110,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cinabre.sudoc.abes.fr/psi_gui/js/bandeau.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1111,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/euopties.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1112,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cknotiz.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1113,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/common/disclaimer/load.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1114,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wpjslib-chunk-notification.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1115,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cpb.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1116,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.tagsOptOut.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1117,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "app.termly.io/embed.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1118,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "static.wpolityce.pl/rhododendron/js/terms_of_service.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1119,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/info_cook.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1120,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/datenschutz.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1121,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "c.sd1.fr/cn/cn.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1122,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "zgody.infor.pl/build/assets/js/main.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1123,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "static.s-sfr.fr/stats/sbtF.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1124,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dsgvo_2018.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1125,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/dsgvo.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1126,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "partner.vxcp.de/_js/vxcp_Common.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1127,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc.js?renew=false&referer=www.rs2.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1128,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/twcdisclaimer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1129,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jqueryCL.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1130,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/trustArcHelper.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1131,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "widget.clym.io/clym.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1132,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "htmedia.in/analytics-js/dap.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1133,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ufti/uftiLoader.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1134,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/includes/pltk/pltk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1135,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/onstuimig-tag-manager/base/adf-tm-base-min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1136,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/GDRP_banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1137,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ads/rgpd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1138,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "samtykker.agm.as/agent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1139,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "samtykker.agdermedia.no/agent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1140,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/PopUpPriva/PopPrivacymin.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1141,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "dialogue.sp-prod.net/messagingWithoutDetection.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"excludedInitiatorDomains": ["globalplayer.com"]
}
},
{
"id": 1142,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/datenschutz.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1143,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccm19.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1144,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/app.dsgvo.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1145,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/trigoAboveBox.jquery.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1146,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dsgvoinit.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1147,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/styleguide/mxqasqco.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1148,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tvp-tcfapi.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1149,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ccm19.de/app/public/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1150,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ccm.ceasy.de/public/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1151,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/orejime/js/orejime.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1152,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccm/public/app.js?apiKey",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1153,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ccm19.de/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1154,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wwwschutz.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1155,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccm19/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1156,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dsvgobanner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1157,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wecoma-lite.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1158,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "resourcesurw.azureedge.net/js/cc55.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1159,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccnst/ccbundle.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1160,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cc.mpa-web.de/public/app.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1161,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/klaro/klaro-pe.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1162,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "klaro-no-css.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1163,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ionic-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1164,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ckpl-webc.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1165,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "wagtail_tag_manager/wtm.bundle.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1166,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "www.capitol.fr/streaming/cnil/cnil.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1167,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Dock/DockContent/Cards/GDPRCard/index.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1168,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "nanoGDPR.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1169,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "newsletters.ftv-preprod.fr/cnil/js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1170,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/components/es6/PrivacyPolicy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1171,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "js.hs-analytics.net/analytics",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1172,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jsdelivr.net/wp/wp-slimstat",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1173,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/dsvgo.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1174,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/GDPR/GDPR.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1175,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "htmedia.in/analytics-js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1176,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/rgpd/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1177,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/avia-bootstrap/js/klaro/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1178,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/tarteaucitron",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1179,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pandectes-core.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1180,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/orejime.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1181,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "jsdelivr.net/npm/cookify",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1182,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cbgCConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1183,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ckpl-webc.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1184,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/klaro-no-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1185,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tcfapi/tcfapi.umd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1186,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "mein.clickskeks.at/app.js?apiKey",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"]
}
},
{
"id": 1187,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consumer/cookie/client",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bt.com"]
}
},
{
"id": 1188,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/frontend/ajax/cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["romstal.ro"]
}
},
{
"id": 1189,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rodo-agreement-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gremimedia.pl"]
}
},
{
"id": 1190,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rodo-agreement-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zw.com.pl"]
}
},
{
"id": 1191,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gtm.js?id=GTM-TCT2RJ",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["fullrate.dk"]
}
},
{
"id": 1192,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["paruvendu.fr"]
}
},
{
"id": 1193,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "datenschutz.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["wumpus-gollum-forum.de"]
}
},
{
"id": 1194,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bho_infobar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ruecken-zentrum.de"]
}
},
{
"id": 1195,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent/message.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ksta.de"]
}
},
{
"id": 1196,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.truste.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["oracle.com"]
}
},
{
"id": 1197,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cui-cookie-policy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["congstar.de"]
}
},
{
"id": 1198,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bitly.com"]
}
},
{
"id": 1199,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy/popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["cdaction.pl"]
}
},
{
"id": 1200,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["knaufdoehetzelf.nl"]
}
},
{
"id": 1201,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["louisvuitton.com"]
}
},
{
"id": 1202,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/spmsg_addetection.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tvspielfilm.de"]
}
},
{
"id": 1203,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/firebox-gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["thelittleboxoffice.com"]
}
},
{
"id": 1204,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.css",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["scholieren.com"]
}
},
{
"id": 1205,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiescript.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["basiszinssatz.de"]
}
},
{
"id": 1206,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies/bundle",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lucozadeenergy.com"]
}
},
{
"id": 1207,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["guitarbackingtrack.com"]
}
},
{
"id": 1208,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ip-consent.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["weersverwachting.nl"]
}
},
{
"id": 1209,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiebot.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["fluke.com"]
}
},
{
"id": 1210,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tui-cookie-bar.html",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tuicruises.com"]
}
},
{
"id": 1211,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-option.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["der-warnemuender.de"]
}
},
{
"id": 1212,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["sexmarkt.nl"]
}
},
{
"id": 1213,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ato-cookiebanner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["haag-streit.com"]
}
},
{
"id": 1214,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["cummins.com"]
}
},
{
"id": 1215,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/spd/spd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["woman.bg"]
}
},
{
"id": 1216,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/spd/spd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["novini.bg"]
}
},
{
"id": 1217,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/spd.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["sportal.bg"]
}
},
{
"id": 1218,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieman.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["erfurter-bahn.de"]
}
},
{
"id": 1219,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rg-gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lombardafiltri.it"]
}
},
{
"id": 1220,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ibox.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["aqualip.de"]
}
},
{
"id": 1221,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cb-scripts",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["convertir-une-image.com"]
}
},
{
"id": 1222,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.teraz.sk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["teraz.sk"]
}
},
{
"id": 1223,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-policy.css",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["independent.com.mt"]
}
},
{
"id": 1224,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/yello-cookie-layer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["yello.de"]
}
},
{
"id": 1225,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["cashconverters.es"]
}
},
{
"id": 1226,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/zsmessagebar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["christian.education"]
}
},
{
"id": 1227,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pmc-pp-tou/privacy.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["rollingstone.com"]
}
},
{
"id": 1228,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["outage.report"]
}
},
{
"id": 1229,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/meWantCookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gamigo.de"]
}
},
{
"id": 1230,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/meWantCookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gamigo.com"]
}
},
{
"id": 1231,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bonniermag.se"]
}
},
{
"id": 1232,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccpa/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lakesideparkmodels.com"]
}
},
{
"id": 1233,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.umdaac.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["yoyogames.com"]
}
},
{
"id": 1234,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/vendor/cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["norma-connect.de"]
}
},
{
"id": 1235,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookiePolicy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["test-aankoop.be"]
}
},
{
"id": 1236,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "aliveachiever.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["dailydot.com"]
}
},
{
"id": 1237,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-bar.salessquad.co.uk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["blochworld.com"]
}
},
{
"id": 1238,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/coobann",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["aida.de"]
}
},
{
"id": 1239,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dsgvoCC.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["strabag-pfs.de"]
}
},
{
"id": 1240,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.euck.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["avon-insurance.co.uk"]
}
},
{
"id": 1241,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/cookie-popup",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["simyo.nl"]
}
},
{
"id": 1242,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bottom.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["herokuapp.com"]
}
},
{
"id": 1243,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["melaniemartinezmusic.com"]
}
},
{
"id": 1244,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cm-body.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tarnkappe.info"]
}
},
{
"id": 1245,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/idgy_gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["metallumnovum.lt"]
}
},
{
"id": 1246,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jw.cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["jacquelinewilson.co.uk"]
}
},
{
"id": 1247,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent_",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["iban-rechner.de"]
}
},
{
"id": 1248,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/kameleoon.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["hanseaticbank.de"]
}
},
{
"id": 1249,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["carriereonline.com"]
}
},
{
"id": 1250,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eloqua",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["visma.fi"]
}
},
{
"id": 1251,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eloqua",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["visma.no"]
}
},
{
"id": 1252,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eloqua",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["visma.se"]
}
},
{
"id": 1253,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eloqua",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["visma.nl"]
}
},
{
"id": 1254,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eloqua",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["visma.com"]
}
},
{
"id": 1255,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_banner_test.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["shinedown.com"]
}
},
{
"id": 1256,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/streamify-gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["streamingbolaget.se"]
}
},
{
"id": 1257,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies_alert.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lineadombra.it"]
}
},
{
"id": 1258,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["kidstaff.com.ua"]
}
},
{
"id": 1259,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["catan.com"]
}
},
{
"id": 1260,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["winbrok.es"]
}
},
{
"id": 1261,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eurogdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["jcbeurope.eu"]
}
},
{
"id": 1262,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ig_cookie_frontend",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["volksbund.de"]
}
},
{
"id": 1263,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "bounceexchange.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lumberliquidators.com"]
}
},
{
"id": 1264,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["keltendorf-mitterkirchen.at"]
}
},
{
"id": 1265,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mitterkirchen.at"]
}
},
{
"id": 1266,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vils.at"]
}
},
{
"id": 1267,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tarteaucitron/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ratp.fr"]
}
},
{
"id": 1268,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "CheckCookiePolicy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["remixshop.com"]
}
},
{
"id": 1269,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/japfg/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["myfridgefood.com"]
}
},
{
"id": 1270,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["raggal.at"]
}
},
{
"id": 1271,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["haugsdorf.at"]
}
},
{
"id": 1272,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["umhausen.at"]
}
},
{
"id": 1273,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookienote/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["wittetools.com"]
}
},
{
"id": 1274,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr-idgy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vejutechnika.lt"]
}
},
{
"id": 1275,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pltk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["goodcontent.pl"]
}
},
{
"id": 1276,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tarteaucitron/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["arena-now.de"]
}
},
{
"id": 1277,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/surbma-yes-no-popup/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bysarahkhan.com"]
}
},
{
"id": 1278,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tryinteract.com"]
}
},
{
"id": 1279,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consentMgmt/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["comdirect.de"]
}
},
{
"id": 1280,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["pasztor.at"]
}
},
{
"id": 1281,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tealium-external/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["weltverbesserer.de"]
}
},
{
"id": 1282,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiesettings.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["aarhusmotion.dk"]
}
},
{
"id": 1283,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/oil",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tv2.dk"]
}
},
{
"id": 1284,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieajx",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["goettgen.de"]
}
},
{
"id": 1285,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.parkers.co.uk",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["parkers.co.uk"]
}
},
{
"id": 1286,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "lovelydrum.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["insidermonkey.com"]
}
},
{
"id": 1287,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/intuCookieConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["intu.co.uk"]
}
},
{
"id": 1288,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tags.tiqcdn.com/utag",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["123-reg.co.uk"]
}
},
{
"id": 1289,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "stormyachiever.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["thewindowsclub.com"]
}
},
{
"id": 1290,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gcb/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gallup.com"]
}
},
{
"id": 1291,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/native-message",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["wiwo.de"]
}
},
{
"id": 1292,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "koekje.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["baustellenabsicherung24.de"]
}
},
{
"id": 1293,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/getdisclaimer",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["wikifolio.com"]
}
},
{
"id": 1294,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/echonetcookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["muenchenticket.de"]
}
},
{
"id": 1295,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies-anekis.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["premiosopenbank.com"]
}
},
{
"id": 1296,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["latoisondor.com"]
}
},
{
"id": 1297,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cbgCConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["carlsberggroup.com"]
}
},
{
"id": 1298,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["jugendregion.at"]
}
},
{
"id": 1299,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp-v2/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["youmath.it"]
}
},
{
"id": 1300,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/permission/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["1und1.de"]
}
},
{
"id": 1301,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "js.driftt.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zenroom.org"]
}
},
{
"id": 1302,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/bwx-cookie-consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nosta.com"]
}
},
{
"id": 1303,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie_settings",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["aerotime.aero"]
}
},
{
"id": 1304,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["stadt-bobingen.de"]
}
},
{
"id": 1305,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "troubledtail.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["interestingengineering.com"]
}
},
{
"id": 1306,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tracking.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["arsys.es"]
}
},
{
"id": 1307,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tracking.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["arsys.fr"]
}
},
{
"id": 1308,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tracking.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["arsys.pt"]
}
},
{
"id": 1309,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tracking.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["arsys.net"]
}
},
{
"id": 1310,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy/Bootstrap",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tescobank.com"]
}
},
{
"id": 1311,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["adlkofen.de"]
}
},
{
"id": 1312,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc_cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["klinikwersbach.de"]
}
},
{
"id": 1313,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookieWall/clb.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["innogy.pl"]
}
},
{
"id": 1314,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["e-pages.dk"]
}
},
{
"id": 1315,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/easycmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lagerhaus.at"]
}
},
{
"id": 1316,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookielab/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ol.fr"]
}
},
{
"id": 1317,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/thcookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["megadruck.de"]
}
},
{
"id": 1318,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["catan.de"]
}
},
{
"id": 1319,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["qcnet.com"]
}
},
{
"id": 1320,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/msmCookieConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["moneysupermarket.com"]
}
},
{
"id": 1321,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eon-com-tracking-consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["eon.com"]
}
},
{
"id": 1322,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/website-cookie-preferences",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nationaltrust.org.uk"]
}
},
{
"id": 1323,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-bundle",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zoopla.co.uk"]
}
},
{
"id": 1324,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-wall",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["xebia.com"]
}
},
{
"id": 1325,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["dhl.com"]
}
},
{
"id": 1326,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cp01.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["jzzo.com"]
}
},
{
"id": 1327,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/klaro/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["salesviewer.com"]
}
},
{
"id": 1328,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr.bundle.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mediacourant.nl"]
}
},
{
"id": 1329,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-management/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["plasteurope.com"]
}
},
{
"id": 1330,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["dragnsurvey.com"]
}
},
{
"id": 1331,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/snap-popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["maggiore.it"]
}
},
{
"id": 1332,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/snap-popup.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["amicoblu.it"]
}
},
{
"id": 1333,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/apprise",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["podrozerowerowe.info"]
}
},
{
"id": 1334,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/data-consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["stadtwerke-luebz.de"]
}
},
{
"id": 1335,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/neo-cookie-layer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["united-domains.de"]
}
},
{
"id": 1336,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieWidget.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["peter-bringts.de"]
}
},
{
"id": 1337,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lux-residence.com"]
}
},
{
"id": 1338,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-manager-v",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["df.eu"]
}
},
{
"id": 1339,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mobilejob.com"]
}
},
{
"id": 1340,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["strato.de"]
}
},
{
"id": 1341,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["strato.nl"]
}
},
{
"id": 1342,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["strato.fr"]
}
},
{
"id": 1343,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["strato.es"]
}
},
{
"id": 1344,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["strato-hosting.co.uk"]
}
},
{
"id": 1345,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["renault.de"]
}
},
{
"id": 1346,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/checkCookieConsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["andrewssykes.fr"]
}
},
{
"id": 1347,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-manager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["cornelsen.de"]
}
},
{
"id": 1348,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["synopsys.com"]
}
},
{
"id": 1349,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["total.com"]
}
},
{
"id": 1350,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tcf2.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["0calc.com"]
}
},
{
"id": 1351,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tcf2.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["0rechner.de"]
}
},
{
"id": 1352,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tcf2.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["0calc.fr"]
}
},
{
"id": 1353,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gusto.at"]
}
},
{
"id": 1354,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dsgvo-opt-in.css",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["kosta.at"]
}
},
{
"id": 1355,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["total.nl"]
}
},
{
"id": 1356,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dywc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["radioschwaben.de"]
}
},
{
"id": 1357,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/microsite-consent-disclaimer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lyxoretf.com"]
}
},
{
"id": 1358,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/eea",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["psychcentral.com"]
}
},
{
"id": 1359,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sm-policy-banner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["storage-mart.com"]
}
},
{
"id": 1360,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/unitb-cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["juedische-allgemeine.de"]
}
},
{
"id": 1361,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["casanetwork.hu"]
}
},
{
"id": 1362,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/legal/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["kamkabel.ru"]
}
},
{
"id": 1363,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/accept.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["oreluniver.ru"]
}
},
{
"id": 1364,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/doria.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lactease.com"]
}
},
{
"id": 1365,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "js.driftt.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ikonltd.co.uk"]
}
},
{
"id": 1366,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/esb-privacy.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["erstebank.hr"]
}
},
{
"id": 1367,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mediamus-cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["swl-unser-stadtwerk.de"]
}
},
{
"id": 1368,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/vinegar.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["stadtwerke-herne.de"]
}
},
{
"id": 1369,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/vhs-assets-cookie-control-js.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["planet-beruf.de"]
}
},
{
"id": 1370,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ob_rgpd/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["orangebank.fr"]
}
},
{
"id": 1371,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mintos.com"]
}
},
{
"id": 1372,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/syno_cookie_element",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["synology.com"]
}
},
{
"id": 1373,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jcookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["javhd.com"]
}
},
{
"id": 1374,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["sparkasse.at"]
}
},
{
"id": 1375,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/PoliticaCookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["fincaraiz.com.co"]
}
},
{
"id": 1376,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Team23_SimpleCookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["kleiderhelden.com"]
}
},
{
"id": 1377,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["esprit.de"]
}
},
{
"id": 1378,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieCutter",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["thebureauinvestigates.com"]
}
},
{
"id": 1379,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ing.es"]
}
},
{
"id": 1380,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tenable.com"]
}
},
{
"id": 1381,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["thetrainline.com"]
}
},
{
"id": 1382,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["op.fi"]
}
},
{
"id": 1383,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.bundle.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["fastbill.com"]
}
},
{
"id": 1384,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gaming-style.com"]
}
},
{
"id": 1385,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["transparenzregister.de"]
}
},
{
"id": 1386,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/uc/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mcmakler.de"]
}
},
{
"id": 1387,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-policy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ubuntu.com"]
}
},
{
"id": 1388,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zitatezumnachdenken.com"]
}
},
{
"id": 1389,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.civiccomputing.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["metoffice.gov.uk"]
}
},
{
"id": 1390,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.civiccomputing.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["churchofengland.org"]
}
},
{
"id": 1391,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cnst.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tauschticket.de"]
}
},
{
"id": 1392,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["liberbank.es"]
}
},
{
"id": 1393,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/iCookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["h-supertools.com"]
}
},
{
"id": 1394,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/he-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["dennree.de"]
}
},
{
"id": 1395,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/uc_cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gastro24.de"]
}
},
{
"id": 1396,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Privacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bopla.de"]
}
},
{
"id": 1397,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["telerama.fr"]
}
},
{
"id": 1398,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["courrierinternational.com"]
}
},
{
"id": 1399,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vodkaster.com"]
}
},
{
"id": 1400,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "brsimg.com/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["boursorama.com"]
}
},
{
"id": 1401,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "trcking.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["canalplus.com"]
}
},
{
"id": 1402,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["hauptbahnhofcity.wien"]
}
},
{
"id": 1403,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["pons.com"]
}
},
{
"id": 1404,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiefly/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["internet-rockstars.com"]
}
},
{
"id": 1405,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["stepstone.de"]
}
},
{
"id": 1406,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["stepstone.at"]
}
},
{
"id": 1407,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["santander.pl"]
}
},
{
"id": 1408,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "trcking.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["canal-plus.com"]
}
},
{
"id": 1409,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["aftenposten.no"]
}
},
{
"id": 1410,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["coca-cola-deutschland.de"]
}
},
{
"id": 1411,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-layer/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bahn.de"]
}
},
{
"id": 1412,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.civiccomputing.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["yodel.co.uk"]
}
},
{
"id": 1413,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["digit-photo.com"]
}
},
{
"id": 1414,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/checkcookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["iracing.com"]
}
},
{
"id": 1415,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["avast.com"]
}
},
{
"id": 1416,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vg.no"]
}
},
{
"id": 1417,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tek.no"]
}
},
{
"id": 1418,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["finn.no"]
}
},
{
"id": 1419,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ice.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lifesycle.co.uk"]
}
},
{
"id": 1420,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["next-immo.com"]
}
},
{
"id": 1421,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fnGdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["financer.com"]
}
},
{
"id": 1422,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-layer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mercedes-benz.io"]
}
},
{
"id": 1423,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/messagingNoTcfApi.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["svd.se"]
}
},
{
"id": 1424,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/sygnal42-gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["blitzrechner.de"]
}
},
{
"id": 1425,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookieLayer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["multipower.com"]
}
},
{
"id": 1426,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_management.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["meteoradar.ch"]
}
},
{
"id": 1427,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-preferences",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["halebop.se"]
}
},
{
"id": 1428,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nif.no"]
}
},
{
"id": 1429,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/klaro-no-css.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["frag-einen-anwalt.de"]
}
},
{
"id": 1430,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["stema.de"]
}
},
{
"id": 1431,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieBox.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vinos.de"]
}
},
{
"id": 1432,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["swisscom.ch"]
}
},
{
"id": 1433,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["schwaebische.de"]
}
},
{
"id": 1434,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["guldsmykket.dk"]
}
},
{
"id": 1435,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mieterengel.de"]
}
},
{
"id": 1436,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/uc_cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gastro-hero.de"]
}
},
{
"id": 1437,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["kurnik.pl"]
}
},
{
"id": 1438,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dg-governance/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nytimes.com"]
}
},
{
"id": 1439,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dg-governance/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["inyt.com"]
}
},
{
"id": 1440,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zukunftsheizen.de"]
}
},
{
"id": 1441,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["truphone.com"]
}
},
{
"id": 1442,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["oka.be"]
}
},
{
"id": 1443,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["prada.com"]
}
},
{
"id": 1444,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["online-spellcheck.com"]
}
},
{
"id": 1445,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/it-cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["geze.de"]
}
},
{
"id": 1446,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/it-cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["geze.pl"]
}
},
{
"id": 1447,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ConsentInit.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["smartphonehoesjes.nl"]
}
},
{
"id": 1448,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieser.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["aco.com.pl"]
}
},
{
"id": 1449,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bluelightcard.co.uk"]
}
},
{
"id": 1450,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/vfConsentCookiesCs.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vodafone.cz"]
}
},
{
"id": 1451,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bt.no"]
}
},
{
"id": 1452,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nwb-jobboerse.de"]
}
},
{
"id": 1453,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/almacmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ampparit.com"]
}
},
{
"id": 1454,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["avg.com"]
}
},
{
"id": 1455,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["washingtonpost.com"]
}
},
{
"id": 1456,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["aftenbladet.no"]
}
},
{
"id": 1457,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_flyout.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["festo.com"]
}
},
{
"id": 1458,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/klaro.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["beckhoff.com"]
}
},
{
"id": 1459,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Manolo_CookieConsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["manoloblahnik.com"]
}
},
{
"id": 1460,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/LdCookieConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["landkreis-eichstaett.de"]
}
},
{
"id": 1461,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/legal-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["meineapotheke.de"]
}
},
{
"id": 1462,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["inishpharmacy.com"]
}
},
{
"id": 1463,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-main",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["godaddy.com"]
}
},
{
"id": 1464,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ultraleicht-trekking.com"]
}
},
{
"id": 1465,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/base-nf.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lecanardenchaine.fr"]
}
},
{
"id": 1466,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "toestemmingen.snp.nl",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["snp.nl"]
}
},
{
"id": 1467,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mendeley.com"]
}
},
{
"id": 1468,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cm/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["howatherm.de"]
}
},
{
"id": 1469,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/osb-cmp.min.mjs",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tvgids.nl"]
}
},
{
"id": 1470,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["01net.com"]
}
},
{
"id": 1471,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.maerkischekiste.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["maerkischekiste.de"]
}
},
{
"id": 1472,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["coca-cola.pl"]
}
},
{
"id": 1473,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["coca-cola.co.uk"]
}
},
{
"id": 1474,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["coca-cola.dk"]
}
},
{
"id": 1475,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/it_nsc.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nonsolocap.it"]
}
},
{
"id": 1476,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.civiccomputing.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["totalwar.com"]
}
},
{
"id": 1477,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gameswelt.ch"]
}
},
{
"id": 1478,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gameswelt.de"]
}
},
{
"id": 1479,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gameswelt.at"]
}
},
{
"id": 1480,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "h1.versicherungsjournal.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["versicherungsjournal.de"]
}
},
{
"id": 1481,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["topannonces.fr"]
}
},
{
"id": 1482,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ellisphere.fr"]
}
},
{
"id": 1483,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mdv.cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["biehler-cycling.com"]
}
},
{
"id": 1484,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["here.com"]
}
},
{
"id": 1485,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gizchina.com"]
}
},
{
"id": 1486,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["consorsbank.de"]
}
},
{
"id": 1487,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/alert-info.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["asus.com"]
}
},
{
"id": 1488,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/alert-info_cn.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["asus.com.cn"]
}
},
{
"id": 1489,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/almacmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["etuovi.com"]
}
},
{
"id": 1490,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/almacmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vuokraovi.com"]
}
},
{
"id": 1491,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/almacmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["arvopaperi.fi"]
}
},
{
"id": 1492,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp.genesis",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vodafone.de"]
}
},
{
"id": 1493,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/newCookieChoice.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vape-phone.fr"]
}
},
{
"id": 1494,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-settings-manager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["aok.de"]
}
},
{
"id": 1495,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Ronis_Cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["slaters.co.uk"]
}
},
{
"id": 1496,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Hmm24_Cookiebanner/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zaehlerschrank24.de"]
}
},
{
"id": 1497,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consensu.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["shinden.pl"]
}
},
{
"id": 1498,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "js_privacy_cmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["e24.no"]
}
},
{
"id": 1499,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookieThough.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["wochenblitz.com"]
}
},
{
"id": 1500,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["beethoven.de"]
}
},
{
"id": 1501,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookieWall/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["eon.pl"]
}
},
{
"id": 1502,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["technics.com"]
}
},
{
"id": 1503,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookiebanner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["unibo.it"]
}
},
{
"id": 1504,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["guloggratis.dk"]
}
},
{
"id": 1505,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "trustarc.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["avantiwestcoast.co.uk"]
}
},
{
"id": 1506,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["stickypassword.com"]
}
},
{
"id": 1507,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_layer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["billiger-mietwagen.de"]
}
},
{
"id": 1508,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/c22.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["cd.cz"]
}
},
{
"id": 1509,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["o2.cz"]
}
},
{
"id": 1510,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tarteaucitron/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["flaine.com"]
}
},
{
"id": 1511,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/almacmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nettikaravaani.com"]
}
},
{
"id": 1512,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/stylecc",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["immozentral.com"]
}
},
{
"id": 1513,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieConsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["loesdau.de"]
}
},
{
"id": 1514,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc_cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zugtouren.de"]
}
},
{
"id": 1515,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["t-mobile.cz"]
}
},
{
"id": 1516,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.civiccomputing.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gfps.com"]
}
},
{
"id": 1517,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jquery.cookiekit.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["dgeg.gov.pt"]
}
},
{
"id": 1518,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["minmote.no"]
}
},
{
"id": 1519,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tc_privacy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["matmut.fr"]
}
},
{
"id": 1520,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie_modal/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["shirtee.com"]
}
},
{
"id": 1521,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/borlabs-cookie",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["finance-magazin.de"]
}
},
{
"id": 1522,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["elle.se"]
}
},
{
"id": 1523,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["fyens.dk"]
}
},
{
"id": 1524,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cf-analytics/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["chatfuel.com"]
}
},
{
"id": 1525,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cc.labu24.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["labu24.de"]
}
},
{
"id": 1526,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.ptj.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ptj.de"]
}
},
{
"id": 1527,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.autopflege24.net",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["autopflege24.net"]
}
},
{
"id": 1528,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["natune.net"]
}
},
{
"id": 1529,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-banner",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["studio3t.com"]
}
},
{
"id": 1530,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies_2020",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["pensionlisboa.com"]
}
},
{
"id": 1531,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ensighten/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["britishairways.com"]
}
},
{
"id": 1532,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ensighten/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ba.com"]
}
},
{
"id": 1533,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["finanz-forum.de"]
}
},
{
"id": 1534,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["konsultacjejst.pl"]
}
},
{
"id": 1535,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/klaro",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tarif4you.de"]
}
},
{
"id": 1536,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ccm.carpassion.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["carpassion.com"]
}
},
{
"id": 1537,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["godt.no"]
}
},
{
"id": 1538,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["pent.no"]
}
},
{
"id": 1539,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["rtr.at"]
}
},
{
"id": 1540,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-policy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["hotmart.com"]
}
},
{
"id": 1541,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["widforss.no"]
}
},
{
"id": 1542,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/acmp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ameblo.jp"]
}
},
{
"id": 1543,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["regentcentre.co.uk"]
}
},
{
"id": 1544,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dkmb_gdpr.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["fightful.com"]
}
},
{
"id": 1545,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pandectes-core.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["arktis.de"]
}
},
{
"id": 1546,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pandectes-core.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["apfelband.de"]
}
},
{
"id": 1547,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/pandectes-core.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["schvarz.com"]
}
},
{
"id": 1548,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/uc-cmp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["santander.de"]
}
},
{
"id": 1549,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookpop.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["electromenager-compare.com"]
}
},
{
"id": 1550,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["klart.se"]
}
},
{
"id": 1551,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tv.nu"]
}
},
{
"id": 1552,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["hemnet.se"]
}
},
{
"id": 1553,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/aw-cookie.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["soprema.fr"]
}
},
{
"id": 1554,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/fc_cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bosch-stiftung.de"]
}
},
{
"id": 1555,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zurich.it"]
}
},
{
"id": 1556,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.lavie.fr",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lavie.fr"]
}
},
{
"id": 1557,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["twisto.pl"]
}
},
{
"id": 1558,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["koestritzer.de"]
}
},
{
"id": 1559,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiestarter",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["postfinance.ch"]
}
},
{
"id": 1560,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/b2c.cookie-consent@latest/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["santanderconsumer.se"]
}
},
{
"id": 1561,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/melindres/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["televes.com"]
}
},
{
"id": 1562,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gdpr/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["broedersgezondheidswinkel.nl"]
}
},
{
"id": 1563,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/DisclaimerControl.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["vontobel.com"]
}
},
{
"id": 1564,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/gtcookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["fixtout.fr"]
}
},
{
"id": 1565,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["le-jackpot-des-medailles-safti.fr"]
}
},
{
"id": 1566,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/RR_KE_ccm19/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["kefalonia-griechenland.com"]
}
},
{
"id": 1567,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/udg-uc-sdk.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["porsche.com"]
}
},
{
"id": 1568,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.civiccomputing.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mann.tv"]
}
},
{
"id": 1569,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-page.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["wz.de"]
}
},
{
"id": 1570,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tarteaucitron.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["intex.fr"]
}
},
{
"id": 1571,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mtb-news.de"]
}
},
{
"id": 1572,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["rennrad-news.de"]
}
},
{
"id": 1573,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["emtb-news.de"]
}
},
{
"id": 1574,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["cyberghostvpn.com"]
}
},
{
"id": 1575,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["playok.com"]
}
},
{
"id": 1576,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "api.usercentrics.eu",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ing.de"]
}
},
{
"id": 1577,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/iceCookie.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mamacar.cz"]
}
},
{
"id": 1578,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/otBannerSdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["springerprofessional.de"]
}
},
{
"id": 1579,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tui.se"]
}
},
{
"id": 1580,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tui.fi"]
}
},
{
"id": 1581,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/index.php?consent_manager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["papierkram.de"]
}
},
{
"id": 1582,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/user-consent-management/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["americanexpress.com"]
}
},
{
"id": 1583,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["privatebanking.hsbc.com"]
}
},
{
"id": 1584,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["hsbc.pl"]
}
},
{
"id": 1585,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/smedia_cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["renzgroup.de"]
}
},
{
"id": 1586,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.tanke-guenstig.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tanke-guenstig.de"]
}
},
{
"id": 1587,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["histoire-et-civilisations.com"]
}
},
{
"id": 1588,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/mfe-cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["santander.com.br"]
}
},
{
"id": 1589,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ccm1.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["dlr.de"]
}
},
{
"id": 1590,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent-manager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["pdf24.org"]
}
},
{
"id": 1591,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.civiccomputing.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lawgazette.co.uk"]
}
},
{
"id": 1592,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "ccm.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["abload.de"]
}
},
{
"id": 1593,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies_utils.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["uca.es"]
}
},
{
"id": 1594,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookie-overlay.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bensanitair.nl"]
}
},
{
"id": 1595,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/boldCookie_custom.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["investice.cz"]
}
},
{
"id": 1596,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmapp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ccm19.de"]
}
},
{
"id": 1597,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cdn.civiccomputing.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gfms.com"]
}
},
{
"id": 1598,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ppp/js/permission-client",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["meinaccount.gmx.net"]
}
},
{
"id": 1599,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ppp/js/permission-client",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["hilfe.gmx.net"]
}
},
{
"id": 1600,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ppp/js/permission-client",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mein.web.de"]
}
},
{
"id": 1601,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "tags.tiqcdn.com/utag",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lineadirecta.com"]
}
},
{
"id": 1602,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ujam_tracking/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ujam.com"]
}
},
{
"id": 1603,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ugp-api/webcontent/v1/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ae.com"]
}
},
{
"id": 1604,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["jobsireland.ie"]
}
},
{
"id": 1605,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "app.usercentrics.eu",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mainpost.de"]
}
},
{
"id": 1606,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "pdcookiepro//views/js/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["czasnaherbate.net"]
}
},
{
"id": 1607,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dtc-fe/policy-control",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["zs-watch.com"]
}
},
{
"id": 1608,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["loveholidays.ie"]
}
},
{
"id": 1609,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/inc/cookie_modal_ajax.php",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["georg.at"]
}
},
{
"id": 1610,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/_next/static/chunks/cookieOverlay",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["galaxus.fr"]
}
},
{
"id": 1611,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.werner-mertz.de",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["werner-mertz.de"]
}
},
{
"id": 1612,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/dist/js/cmp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["corporate.modivo.com"]
}
},
{
"id": 1613,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/jimmsconsent",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["jimms.fi"]
}
},
{
"id": 1614,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "chunk-cookie-consent-modal",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["jobup.ch"]
}
},
{
"id": 1615,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Cookie/ccm19/public/index.php/app.js?",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["condair.de"]
}
},
{
"id": 1616,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["eurostar.com"]
}
},
{
"id": 1617,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/static/ct/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["wko.at"]
}
},
{
"id": 1618,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ui/common/scripts/cookies/cookieModalComponent-797ec8a07a.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["oem.no"]
}
},
{
"id": 1619,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["politico.eu"]
}
},
{
"id": 1620,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lovelybooks.de"]
}
},
{
"id": 1621,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["idealo.de"]
}
},
{
"id": 1622,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["idealo.at"]
}
},
{
"id": 1623,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["idealo.es"]
}
},
{
"id": 1624,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["idealo.fr"]
}
},
{
"id": 1625,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["idealo.it"]
}
},
{
"id": 1626,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["idealo.co.uk"]
}
},
{
"id": 1627,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["femmeactuelle.fr"]
}
},
{
"id": 1628,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["hausjournal.net"]
}
},
{
"id": 1629,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["lepoint.fr"]
}
},
{
"id": 1630,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["chefkoch.de"]
}
},
{
"id": 1631,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["boerse.de"]
}
},
{
"id": 1632,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["liberation.fr"]
}
},
{
"id": 1633,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["caradisiac.com"]
}
},
{
"id": 1634,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wrapperMessagingWithoutDetection",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["anderes-wort.de"]
}
},
{
"id": 1635,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CookieBanner.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bauer-baumschulen.ch"]
}
},
{
"id": 1636,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["strato.se"]
}
},
{
"id": 1637,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["imusic.de"]
}
},
{
"id": 1638,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["imusic.dk"]
}
},
{
"id": 1639,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["imusic.no"]
}
},
{
"id": 1640,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["imusic.se"]
}
},
{
"id": 1641,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cc.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["imusic.co"]
}
},
{
"id": 1642,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["comparethemarket.com"]
}
},
{
"id": 1643,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["malservice.aftonbladet.se"]
}
},
{
"id": 1644,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/brabo-cookie/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["brandpreventiewinkel.nl"]
}
},
{
"id": 1645,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/almacmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nettimoto.com"]
}
},
{
"id": 1646,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-center.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["futura-sciences.com"]
}
},
{
"id": 1647,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-box",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["schmalz.com"]
}
},
{
"id": 1648,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/popupConsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["the-fence.com"]
}
},
{
"id": 1649,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-files/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["paradoxwikis.com"]
}
},
{
"id": 1650,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookieconsent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["expert.es"]
}
},
{
"id": 1651,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.quantcast.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gettr.com"]
}
},
{
"id": 1652,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "coco.we-online.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["we-online.com"]
}
},
{
"id": 1653,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/modulos/cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["villagrancanaria.com"]
}
},
{
"id": 1654,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["carhartt.com"]
}
},
{
"id": 1655,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/almacmp",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["kotikokki.net"]
}
},
{
"id": 1656,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rcs_cpmt/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["open.online"]
}
},
{
"id": 1657,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rcs_cpmt/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["style.corriere.it"]
}
},
{
"id": 1658,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ph-cookie-helper-mu/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["uwartsonline.nl"]
}
},
{
"id": 1659,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": ".consent-",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["pcgames.de"]
}
},
{
"id": 1660,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.quantcast.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["deviantart.com"]
}
},
{
"id": 1661,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["omni.se"]
}
},
{
"id": 1662,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Libs/cookies",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nadeta.cz"]
}
},
{
"id": 1663,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/standard-cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["standard.sk"]
}
},
{
"id": 1664,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Disclaimer.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["private.com"]
}
},
{
"id": 1665,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["seeandso.com"]
}
},
{
"id": 1666,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/privacy/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["patente.it"]
}
},
{
"id": 1667,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/utag.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["tk-aerztefuehrer.de"]
}
},
{
"id": 1668,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/rcs_cpmt/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["giroditalia.it"]
}
},
{
"id": 1669,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["behindthename.com"]
}
},
{
"id": 1670,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "it-cc.index.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["boellhoff.com"]
}
},
{
"id": 1671,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiecontrol.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["rembutiken.se"]
}
},
{
"id": 1672,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["deutsches-schulportal.de"]
}
},
{
"id": 1673,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Custom_clubhinweis",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["ofdb.de"]
}
},
{
"id": 1674,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-center.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["elpais.com"]
}
},
{
"id": 1675,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.quantcast.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["pr0gramm.com"]
}
},
{
"id": 1676,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "cmp.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["helthjem.no"]
}
},
{
"id": 1677,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["webkamery.online"]
}
},
{
"id": 1678,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/infrasdk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["nofluffjobs.com"]
}
},
{
"id": 1679,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-center.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["diariodejerez.es"]
}
},
{
"id": 1680,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-center.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["leonoticias.com"]
}
},
{
"id": 1681,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookies_master.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["bayern.de"]
}
},
{
"id": 1682,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-center.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["france24.com"]
}
},
{
"id": 1683,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/CKCookieConsent.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["caseking.de"]
}
},
{
"id": 1684,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gea-waldviertler.at"]
}
},
{
"id": 1685,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookie-consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["gea-waldviertler.de"]
}
},
{
"id": 1686,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ccm_",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["kro-ncrv.nl"]
}
},
{
"id": 1687,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-center.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["retinatendencias.com"]
}
},
{
"id": 1688,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/tac.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["avenuedelabrique.com"]
}
},
{
"id": 1689,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-center.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["eldiadecordoba.es"]
}
},
{
"id": 1690,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/ConsentManager/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["flvw.de"]
}
},
{
"id": 1691,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "analytics-consent-manager",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["terveystalo.com"]
}
},
{
"id": 1692,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "seznam.cz",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["firmy.cz"]
}
},
{
"id": 1693,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-center.org",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["fameplay.tv"]
}
},
{
"id": 1694,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "poool.fr/access.min.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["spektrum.de"]
}
},
{
"id": 1695,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/wimc_gtm_consent/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["wingo.ch"]
}
},
{
"id": 1696,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp_puk.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["commerzbank.de"]
}
},
{
"id": 1697,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/scmp-int.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["mapy.cz"]
}
},
{
"id": 1698,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "privacy-mgmt.com",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["immobilien.derstandard.at"]
}
},
{
"id": 1699,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/js/privacy",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["atu.de"]
}
},
{
"id": 1700,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/consent.",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["onlinestempel.ch"]
}
},
{
"id": 1701,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cookiewall/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["saeco.de"]
}
},
{
"id": 1702,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/Cookies.js",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["sage.co.uk"]
}
},
{
"id": 1703,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "/cmp/",
"resourceTypes": ["script", "stylesheet", "xmlhttprequest", "image"],
"initiatorDomains": ["liebherr.com"]
}
}
]
================================================
FILE: pkg/engine/headless/browser/element.go
================================================
package browser
import (
"net/url"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/projectdiscovery/katana/pkg/engine/headless/types"
)
const (
// buttonsCSSSelector is the css selector for all buttons
buttonsCSSSelector = "button, input[type='button'], input[type='submit']"
// linksCSSSelector is the css selector for all anchor tags
linksCSSSelector = "a"
)
// isElementDisabled checks if a button element is disabled
func isElementDisabled(element *types.HTMLElement) bool {
if element.Attributes == nil {
return false
}
// Standard HTML disabled attribute
if _, disabled := element.Attributes["disabled"]; disabled {
return true
}
// Tailwind or framework class-based detection
if classAttr, ok := element.Attributes["class"]; ok {
classList := strings.Fields(classAttr)
for _, class := range classList {
if class == "cursor-not-allowed" || class == "pointer-events-none" {
return true
}
}
}
// Optionally support ARIA disabled
if aria, ok := element.Attributes["aria-disabled"]; ok && (aria == "true" || aria == "1") {
return true
}
return false
}
// FindNavigation attempts to find more navigations on the page which could
// be done to find more links and pages.
//
// This includes the following -
// 1. Forms
// 2. Buttons
// 3. Links
// 4. Elements with event listeners
//
// The navigations found are unique across the page. The caller
// needs to ensure they are unique globally before doing further actions with details.
func (b *BrowserPage) FindNavigations() ([]*types.Action, error) {
unique := make(map[string]struct{})
navigations := make([]*types.Action, 0)
forms, err := b.GetAllForms()
if err != nil {
return nil, errors.Wrap(err, "could not get forms")
}
for _, form := range forms {
for _, element := range form.Elements {
if element.TagName != "BUTTON" {
continue
}
// TODO: Check if this button is already in the unique map
// and if so remove it
unique[element.Hash()] = struct{}{}
}
hash := form.Hash()
if _, found := unique[hash]; found {
continue
}
unique[hash] = struct{}{}
navigations = append(navigations, &types.Action{
Type: types.ActionTypeFillForm,
Form: form,
})
}
buttons, err := b.GetAllElements(buttonsCSSSelector)
if err != nil {
return nil, errors.Wrap(err, "could not get buttons")
}
for _, button := range buttons {
if isElementDisabled(button) {
continue
}
hash := button.Hash()
button.MD5Hash = hash
if _, found := unique[hash]; found {
continue
}
unique[hash] = struct{}{}
navigations = append(navigations, &types.Action{
Type: types.ActionTypeLeftClick,
Element: button,
})
}
scopeValidator := b.launcher.ScopeValidator()
links, err := b.GetAllElements(linksCSSSelector)
if err != nil {
return nil, errors.Wrap(err, "could not get links")
}
info, err := b.Info()
if err != nil {
return nil, errors.Wrap(err, "could not get page info")
}
for _, link := range links {
href := link.Attributes["href"]
if href == "" {
continue
}
resolvedHref, err := resolveURL(info.URL, href)
if err != nil {
continue
}
u, err := url.Parse(resolvedHref)
if err != nil {
continue
}
if u.Scheme != "http" && u.Scheme != "https" {
continue
}
if !scopeValidator(resolvedHref) {
continue
}
hash := link.Hash()
link.MD5Hash = hash
if _, found := unique[hash]; found {
continue
}
unique[hash] = struct{}{}
navigations = append(navigations, &types.Action{
Type: types.ActionTypeLeftClick,
Element: link,
})
}
eventListeners, err := b.GetEventListeners()
if err != nil {
return nil, errors.Wrap(err, "could not get event listeners")
}
for _, listener := range eventListeners {
if _, found := relevantEventListeners[listener.Type]; !found {
continue
}
if listener.Element == nil {
continue
}
hash := listener.Element.Hash()
listener.Element.MD5Hash = hash
if _, found := unique[hash]; found {
continue
}
unique[hash] = struct{}{}
navigations = append(navigations, types.ActionFromEventListener(listener))
}
return navigations, nil
}
func (b *BrowserPage) GetAllElements(selector string) ([]*types.HTMLElement, error) {
objects, err := b.Eval(`() => window.getAllElements(` + strconv.Quote(selector) + `)`)
if err != nil {
return nil, err
}
elements := make([]*types.HTMLElement, 0)
if err := objects.Value.Unmarshal(&elements); err != nil {
return nil, err
}
return elements, nil
}
func (b *BrowserPage) GetElementFromXpath(xpath string) (*types.HTMLElement, error) {
object, err := b.Eval(`() => window.getElementFromXPath(` + strconv.Quote(xpath) + `)`)
if err != nil {
return nil, err
}
element := &types.HTMLElement{}
if err := object.Value.Unmarshal(element); err != nil {
return nil, err
}
return element, nil
}
func (b *BrowserPage) GetAllForms() ([]*types.HTMLForm, error) {
objects, err := b.Eval(`() => window.getAllForms()`)
if err != nil {
return nil, err
}
elements := make([]*types.HTMLForm, 0)
if err := objects.Value.Unmarshal(&elements); err != nil {
return nil, err
}
return elements, nil
}
// GetEventListeners returns all event listeners on the page
func (b *BrowserPage) GetEventListeners() ([]*types.EventListener, error) {
listeners := make([]*types.EventListener, 0)
eventlisteners, err := b.Eval(`() => window.__eventListeners`)
if err == nil {
_ = eventlisteners.Value.Unmarshal(&listeners)
}
// Also get inline event listeners
var inlineEventListeners []struct {
Element *types.HTMLElement `json:"element"`
Listeners []struct {
Type string `json:"type"`
Listener string `json:"listener"`
} `json:"listeners"`
}
inlineListeners, err := b.Eval(`() => window.getAllElementsWithEventListeners()`)
if err != nil {
return nil, err
}
if err := inlineListeners.Value.Unmarshal(&inlineEventListeners); err != nil {
return nil, err
}
for _, inlineListener := range inlineEventListeners {
for _, listener := range inlineListener.Listeners {
listenerType := strings.TrimPrefix(listener.Type, "on")
listeners = append(listeners, &types.EventListener{
Type: listenerType,
Listener: listener.Listener,
Element: inlineListener.Element,
})
}
}
return listeners, nil
}
// NavigatedLink is a link navigated collected from one of the
// navigation hooks.
type NavigatedLink struct {
URL string `json:"url"`
Source string `json:"source"`
}
// GetNavigatedLinks returns all navigated links on the page
func (b *BrowserPage) GetNavigatedLinks() ([]*NavigatedLink, error) {
navigatedLinks, err := b.Eval(`() => window.__navigatedLinks`)
if err != nil {
return nil, err
}
listeners := make([]*NavigatedLink, 0)
if err := navigatedLinks.Value.Unmarshal(&listeners); err != nil {
return nil, err
}
return listeners, nil
}
// Define the map to hold event types
var relevantEventListeners = map[string]struct{}{
// Focus and Blur events
"focusin": {},
"focus": {},
"blur": {},
"focusout": {},
// Click and Mouse events
"click": {},
"auxclick": {},
"mousedown": {},
"mouseup": {},
"dblclick": {},
"mouseover": {},
"mouseenter": {},
"mouseleave": {},
"mouseout": {},
"wheel": {},
"contextmenu": {},
// Key events
"keydown": {},
"keypress": {},
"keyup": {},
// Form events
"submit": {},
"input": {},
"change": {},
}
// resolveURL resolves a potentially relative URL against a base URL
func resolveURL(baseURLStr, href string) (string, error) {
baseURL, err := url.Parse(baseURLStr)
if err != nil {
return "", err
}
resolvedURL, err := baseURL.Parse(href)
if err != nil {
return "", err
}
return resolvedURL.String(), nil
}
================================================
FILE: pkg/engine/headless/browser/stealth/assets.go
================================================
// from stealth go-rod
package stealth
// JS for stealth
const JS = `;(() => {
(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:'utils => {\n if (!window.chrome) {\n // Use the exact property descriptor found in headful Chrome\n // fetch it via ` + "`" + `Object.getOwnPropertyDescriptor(window, \'chrome\')` + "`" + `\n Object.defineProperty(window, \'chrome\', {\n writable: true,\n enumerable: true,\n configurable: false, // note!\n value: {} // We\'ll extend that later\n })\n }\n\n // That means we\'re running headful and don\'t need to mock anything\n if (\'app\' in window.chrome) {\n return // Nothing to do here\n }\n\n const makeError = {\n ErrorInInvocation: fn => {\n const err = new TypeError(` + "`" + `Error in invocation of app.${fn}()` + "`" + `)\n return utils.stripErrorWithAnchor(\n err,\n ` + "`" + `at ${fn} (eval at ` + "`" + `\n )\n }\n }\n\n // There\'s a some static data in that property which doesn\'t seem to change,\n // we should periodically check for updates: ` + "`" + `JSON.stringify(window.app, null, 2)` + "`" + `\n const STATIC_DATA = JSON.parse(\n ` + "`" + `\n{\n "isInstalled": false,\n "InstallState": {\n "DISABLED": "disabled",\n "INSTALLED": "installed",\n "NOT_INSTALLED": "not_installed"\n },\n "RunningState": {\n "CANNOT_RUN": "cannot_run",\n "READY_TO_RUN": "ready_to_run",\n "RUNNING": "running"\n }\n}\n ` + "`" + `.trim()\n )\n\n window.chrome.app = {\n ...STATIC_DATA,\n\n get isInstalled() {\n return false\n },\n\n getDetails: function getDetails() {\n if (arguments.length) {\n throw makeError.ErrorInInvocation(` + "`" + `getDetails` + "`" + `)\n }\n return null\n },\n getIsInstalled: function getDetails() {\n if (arguments.length) {\n throw makeError.ErrorInInvocation(` + "`" + `getIsInstalled` + "`" + `)\n }\n return false\n },\n runningState: function getDetails() {\n if (arguments.length) {\n throw makeError.ErrorInInvocation(` + "`" + `runningState` + "`" + `)\n }\n return \'cannot_run\'\n }\n }\n utils.patchToStringNested(window.chrome.app)\n }',_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"utils => {\n if (!window.chrome) {\n // Use the exact property descriptor found in headful Chrome\n // fetch it via ` + "`" + `Object.getOwnPropertyDescriptor(window, 'chrome')` + "`" + `\n Object.defineProperty(window, 'chrome', {\n writable: true,\n enumerable: true,\n configurable: false, // note!\n value: {} // We'll extend that later\n })\n }\n\n // That means we're running headful and don't need to mock anything\n if ('csi' in window.chrome) {\n return // Nothing to do here\n }\n\n // Check that the Navigation Timing API v1 is available, we need that\n if (!window.performance || !window.performance.timing) {\n return\n }\n\n const { timing } = window.performance\n\n window.chrome.csi = function() {\n return {\n onloadT: timing.domContentLoadedEventEnd,\n startE: timing.navigationStart,\n pageT: Date.now() - timing.navigationStart,\n tran: 15 // Transition type or something\n }\n }\n utils.patchToString(window.chrome.csi)\n }",_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"(utils, { opts }) => {\n if (!window.chrome) {\n // Use the exact property descriptor found in headful Chrome\n // fetch it via ` + "`" + `Object.getOwnPropertyDescriptor(window, 'chrome')` + "`" + `\n Object.defineProperty(window, 'chrome', {\n writable: true,\n enumerable: true,\n configurable: false, // note!\n value: {} // We'll extend that later\n })\n }\n\n // That means we're running headful and don't need to mock anything\n if ('loadTimes' in window.chrome) {\n return // Nothing to do here\n }\n\n // Check that the Navigation Timing API v1 + v2 is available, we need that\n if (\n !window.performance ||\n !window.performance.timing ||\n !window.PerformancePaintTiming\n ) {\n return\n }\n\n const { performance } = window\n\n // Some stuff is not available on about:blank as it requires a navigation to occur,\n // let's harden the code to not fail then:\n const ntEntryFallback = {\n nextHopProtocol: 'h2',\n type: 'other'\n }\n\n // The API exposes some funky info regarding the connection\n const protocolInfo = {\n get connectionInfo() {\n const ntEntry =\n performance.getEntriesByType('navigation')[0] || ntEntryFallback\n return ntEntry.nextHopProtocol\n },\n get npnNegotiatedProtocol() {\n // NPN is deprecated in favor of ALPN, but this implementation returns the\n // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.\n const ntEntry =\n performance.getEntriesByType('navigation')[0] || ntEntryFallback\n return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n ? ntEntry.nextHopProtocol\n : 'unknown'\n },\n get navigationType() {\n const ntEntry =\n performance.getEntriesByType('navigation')[0] || ntEntryFallback\n return ntEntry.type\n },\n get wasAlternateProtocolAvailable() {\n // The Alternate-Protocol header is deprecated in favor of Alt-Svc\n // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically this\n // should always return false.\n return false\n },\n get wasFetchedViaSpdy() {\n // SPDY is deprecated in favor of HTTP/2, but this implementation returns\n // true for HTTP/2 or HTTP2+QUIC/39 as well.\n const ntEntry =\n performance.getEntriesByType('navigation')[0] || ntEntryFallback\n return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n },\n get wasNpnNegotiated() {\n // NPN is deprecated in favor of ALPN, but this implementation returns true\n // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.\n const ntEntry =\n performance.getEntriesByType('navigation')[0] || ntEntryFallback\n return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\n }\n }\n\n const { timing } = window.performance\n\n // Truncate number to specific number of decimals, most of the ` + "`" + `loadTimes` + "`" + ` stuff has 3\n function toFixed(num, fixed) {\n var re = new RegExp('^-?\\\\d+(?:.\\\\d{0,' + (fixed || -1) + '})?')\n return num.toString().match(re)[0]\n }\n\n const timingInfo = {\n get firstPaintAfterLoadTime() {\n // This was never actually implemented and always returns 0.\n return 0\n },\n get requestTime() {\n return timing.navigationStart / 1000\n },\n get startLoadTime() {\n return timing.navigationStart / 1000\n },\n get commitLoadTime() {\n return timing.responseStart / 1000\n },\n get finishDocumentLoadTime() {\n return timing.domContentLoadedEventEnd / 1000\n },\n get finishLoadTime() {\n return timing.loadEventEnd / 1000\n },\n get firstPaintTime() {\n const fpEntry = performance.getEntriesByType('paint')[0] || {\n startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occurred (` + "`" + `about:blank` + "`" + `)\n }\n return toFixed(\n (fpEntry.startTime + performance.timeOrigin) / 1000,\n 3\n )\n }\n }\n\n window.chrome.loadTimes = function() {\n return {\n ...protocolInfo,\n ...timingInfo\n }\n }\n utils.patchToString(window.chrome.loadTimes)\n }",_args:[{opts:{}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"(utils, { opts, STATIC_DATA }) => {\n if (!window.chrome) {\n // Use the exact property descriptor found in headful Chrome\n // fetch it via ` + "`" + `Object.getOwnPropertyDescriptor(window, 'chrome')` + "`" + `\n Object.defineProperty(window, 'chrome', {\n writable: true,\n enumerable: true,\n configurable: false, // note!\n value: {} // We'll extend that later\n })\n }\n\n // That means we're running headful and don't need to mock anything\n const existsAlready = 'runtime' in window.chrome\n // ` + "`" + `chrome.runtime` + "`" + ` is only exposed on secure origins\n const isNotSecure = !window.location.protocol.startsWith('https')\n if (existsAlready || (isNotSecure && !opts.runOnInsecureOrigins)) {\n return // Nothing to do here\n }\n\n window.chrome.runtime = {\n // There's a bunch of static data in that property which doesn't seem to change,\n // we should periodically check for updates: ` + "`" + `JSON.stringify(window.chrome.runtime, null, 2)` + "`" + `\n ...STATIC_DATA,\n // ` + "`" + `chrome.runtime.id` + "`" + ` is extension related and returns undefined in Chrome\n get id() {\n return undefined\n },\n // These two require more sophisticated mocks\n connect: null,\n sendMessage: null\n }\n\n const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({\n NoMatchingSignature: new TypeError(\n preamble + ` + "`" + `No matching signature.` + "`" + `\n ),\n MustSpecifyExtensionID: new TypeError(\n preamble +\n ` + "`" + `${method} called from a webpage must specify an Extension ID (string) for its first argument.` + "`" + `\n ),\n InvalidExtensionID: new TypeError(\n preamble + ` + "`" + `Invalid extension id: '${extensionId}'` + "`" + `\n )\n })\n\n // Valid Extension IDs are 32 characters in length and use the letter ` + "`" + `a` + "`" + ` to ` + "`" + `p` + "`" + `:\n // https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90\n const isValidExtensionID = str =>\n str.length === 32 && str.toLowerCase().match(/^[a-p]+$/)\n\n /** Mock ` + "`" + `chrome.runtime.sendMessage` + "`" + ` */\n const sendMessageHandler = {\n apply: function(target, ctx, args) {\n const [extensionId, options, responseCallback] = args || []\n\n // Define custom errors\n const errorPreamble = ` + "`" + `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): ` + "`" + `\n const Errors = makeCustomRuntimeErrors(\n errorPreamble,\n ` + "`" + `chrome.runtime.sendMessage()` + "`" + `,\n extensionId\n )\n\n // Check if the call signature looks ok\n const noArguments = args.length === 0\n const tooManyArguments = args.length > 4\n const incorrectOptions = options && typeof options !== 'object'\n const incorrectResponseCallback =\n responseCallback && typeof responseCallback !== 'function'\n if (\n noArguments ||\n tooManyArguments ||\n incorrectOptions ||\n incorrectResponseCallback\n ) {\n throw Errors.NoMatchingSignature\n }\n\n // At least 2 arguments are required before we even validate the extension ID\n if (args.length < 2) {\n throw Errors.MustSpecifyExtensionID\n }\n\n // Now let's make sure we got a string as extension ID\n if (typeof extensionId !== 'string') {\n throw Errors.NoMatchingSignature\n }\n\n if (!isValidExtensionID(extensionId)) {\n throw Errors.InvalidExtensionID\n }\n\n return undefined // Normal behavior\n }\n }\n utils.mockWithProxy(\n window.chrome.runtime,\n 'sendMessage',\n function sendMessage() {},\n sendMessageHandler\n )\n\n /**\n * Mock ` + "`" + `chrome.runtime.connect` + "`" + `\n *\n * @see https://developer.chrome.com/apps/runtime#method-connect\n */\n const connectHandler = {\n apply: function(target, ctx, args) {\n const [extensionId, connectInfo] = args || []\n\n // Define custom errors\n const errorPreamble = ` + "`" + `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): ` + "`" + `\n const Errors = makeCustomRuntimeErrors(\n errorPreamble,\n ` + "`" + `chrome.runtime.connect()` + "`" + `,\n extensionId\n )\n\n // Behavior differs a bit from sendMessage:\n const noArguments = args.length === 0\n const emptyStringArgument = args.length === 1 && extensionId === ''\n if (noArguments || emptyStringArgument) {\n throw Errors.MustSpecifyExtensionID\n }\n\n const tooManyArguments = args.length > 2\n const incorrectConnectInfoType =\n connectInfo && typeof connectInfo !== 'object'\n\n if (tooManyArguments || incorrectConnectInfoType) {\n throw Errors.NoMatchingSignature\n }\n\n const extensionIdIsString = typeof extensionId === 'string'\n if (extensionIdIsString && extensionId === '') {\n throw Errors.MustSpecifyExtensionID\n }\n if (extensionIdIsString && !isValidExtensionID(extensionId)) {\n throw Errors.InvalidExtensionID\n }\n\n // There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validate\n const validateConnectInfo = ci => {\n // More than a first param connectInfo as been provided\n if (args.length > 1) {\n throw Errors.NoMatchingSignature\n }\n // An empty connectInfo has been provided\n if (Object.keys(ci).length === 0) {\n throw Errors.MustSpecifyExtensionID\n }\n // Loop over all connectInfo props an check them\n Object.entries(ci).forEach(([k, v]) => {\n const isExpected = ['name', 'includeTlsChannelId'].includes(k)\n if (!isExpected) {\n throw new TypeError(\n errorPreamble + ` + "`" + `Unexpected property: '${k}'.` + "`" + `\n )\n }\n const MismatchError = (propName, expected, found) =>\n TypeError(\n errorPreamble +\n ` + "`" + `Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.` + "`" + `\n )\n if (k === 'name' && typeof v !== 'string') {\n throw MismatchError(k, 'string', typeof v)\n }\n if (k === 'includeTlsChannelId' && typeof v !== 'boolean') {\n throw MismatchError(k, 'boolean', typeof v)\n }\n })\n }\n if (typeof extensionId === 'object') {\n validateConnectInfo(extensionId)\n throw Errors.MustSpecifyExtensionID\n }\n\n // Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as well\n return utils.patchToStringNested(makeConnectResponse())\n }\n }\n utils.mockWithProxy(\n window.chrome.runtime,\n 'connect',\n function connect() {},\n connectHandler\n )\n\n function makeConnectResponse() {\n const onSomething = () => ({\n addListener: function addListener() {},\n dispatch: function dispatch() {},\n hasListener: function hasListener() {},\n hasListeners: function hasListeners() {\n return false\n },\n removeListener: function removeListener() {}\n })\n\n const response = {\n name: '',\n sender: undefined,\n disconnect: function disconnect() {},\n onDisconnect: onSomething(),\n onMessage: onSomething(),\n postMessage: function postMessage() {\n if (!arguments.length) {\n throw new TypeError(` + "`" + `Insufficient number of arguments.` + "`" + `)\n }\n throw new Error(` + "`" + `Attempting to use a disconnected port object` + "`" + `)\n }\n }\n return response\n }\n }",_args:[{opts:{runOnInsecureOrigins:!1},STATIC_DATA:{OnInstalledReason:{CHROME_UPDATE:"chrome_update",INSTALL:"install",SHARED_MODULE_UPDATE:"shared_module_update",UPDATE:"update"},OnRestartRequiredReason:{APP_UPDATE:"app_update",OS_UPDATE:"os_update",PERIODIC:"periodic"},PlatformArch:{ARM:"arm",ARM64:"arm64",MIPS:"mips",MIPS64:"mips64",X86_32:"x86-32",X86_64:"x86-64"},PlatformNaclArch:{ARM:"arm",MIPS:"mips",MIPS64:"mips64",X86_32:"x86-32",X86_64:"x86-64"},PlatformOs:{ANDROID:"android",CROS:"cros",LINUX:"linux",MAC:"mac",OPENBSD:"openbsd",WIN:"win"},RequestUpdateCheckStatus:{NO_UPDATE:"no_update",THROTTLED:"throttled",UPDATE_AVAILABLE:"update_available"}}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"utils => {\n /**\n * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing.\n *\n * @example\n * video/webm; codecs=\"vp8, vorbis\"\n * video/mp4; codecs=\"avc1.42E01E\"\n * audio/x-m4a;\n * audio/ogg; codecs=\"vorbis\"\n * @param {String} arg\n */\n const parseInput = arg => {\n const [mime, codecStr] = arg.trim().split(';')\n let codecs = []\n if (codecStr && codecStr.includes('codecs=\"')) {\n codecs = codecStr\n .trim()\n .replace(` + "`" + `codecs=\"` + "`" + `, '')\n .replace(` + "`" + `\"` + "`" + `, '')\n .trim()\n .split(',')\n .filter(x => !!x)\n .map(x => x.trim())\n }\n return {\n mime,\n codecStr,\n codecs\n }\n }\n\n const canPlayType = {\n // Intercept certain requests\n apply: function(target, ctx, args) {\n if (!args || !args.length) {\n return target.apply(ctx, args)\n }\n const { mime, codecs } = parseInput(args[0])\n // This specific mp4 codec is missing in Chromium\n if (mime === 'video/mp4') {\n if (codecs.includes('avc1.42E01E')) {\n return 'probably'\n }\n }\n // This mimetype is only supported if no codecs are specified\n if (mime === 'audio/x-m4a' && !codecs.length) {\n return 'maybe'\n }\n\n // This mimetype is only supported if no codecs are specified\n if (mime === 'audio/aac' && !codecs.length) {\n return 'probably'\n }\n // Everything else as usual\n return target.apply(ctx, args)\n }\n }\n\n /* global HTMLMediaElement */\n utils.replaceWithProxy(\n HTMLMediaElement.prototype,\n 'canPlayType',\n canPlayType\n )\n }",_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"(utils, { opts }) => {\n utils.replaceGetterWithProxy(\n Object.getPrototypeOf(navigator),\n 'hardwareConcurrency',\n utils.makeHandler().getterValue(opts.hardwareConcurrency)\n )\n }",_args:[{opts:{hardwareConcurrency:4}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"(utils, { opts }) => {\n const languages = opts.languages.length\n ? opts.languages\n : ['en-US', 'en']\n utils.replaceGetterWithProxy(\n Object.getPrototypeOf(navigator),\n 'languages',\n utils.makeHandler().getterValue(Object.freeze([...languages]))\n )\n }",_args:[{opts:{languages:[]}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"(utils, opts) => {\n const isSecure = document.location.protocol.startsWith('https')\n\n // In headful on secure origins the permission should be \"default\", not \"denied\"\n if (isSecure) {\n utils.replaceGetterWithProxy(Notification, 'permission', {\n apply() {\n return 'default'\n }\n })\n }\n\n // Another weird behavior:\n // On insecure origins in headful the state is \"denied\",\n // whereas in headless it's \"prompt\"\n if (!isSecure) {\n const handler = {\n apply(target, ctx, args) {\n const param = (args || [])[0]\n\n const isNotifications =\n param && param.name && param.name === 'notifications'\n if (!isNotifications) {\n return utils.cache.Reflect.apply(...arguments)\n }\n\n return Promise.resolve(\n Object.setPrototypeOf(\n {\n state: 'denied',\n onchange: null\n },\n PermissionStatus.prototype\n )\n )\n }\n }\n // Note: Don't use ` + "`" + `Object.getPrototypeOf` + "`" + ` here\n utils.replaceWithProxy(Permissions.prototype, 'query', handler)\n }\n }",_args:[{}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"(utils, { fns, data }) => {\n fns = utils.materializeFns(fns)\n\n // That means we're running headful\n const hasPlugins = 'plugins' in navigator && navigator.plugins.length\n if (hasPlugins) {\n return // nothing to do here\n }\n\n const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes)\n const plugins = fns.generatePluginArray(utils, fns)(data.plugins)\n\n // Plugin and MimeType cross-reference each other, let's do that now\n // Note: We're looping through ` + "`" + `data.plugins` + "`" + ` here, not the generated ` + "`" + `plugins` + "`" + `\n for (const pluginData of data.plugins) {\n pluginData.__mimeTypes.forEach((type, index) => {\n plugins[pluginData.name][index] = mimeTypes[type]\n\n Object.defineProperty(plugins[pluginData.name], type, {\n value: mimeTypes[type],\n writable: false,\n enumerable: false, // Not enumerable\n configurable: true\n })\n Object.defineProperty(mimeTypes[type], 'enabledPlugin', {\n value:\n type === 'application/x-pnacl'\n ? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaks\n : new Proxy(plugins[pluginData.name], {}), // Prevent circular references\n writable: false,\n enumerable: false, // Important: ` + "`" + `JSON.stringify(navigator.plugins)` + "`" + `\n configurable: true\n })\n })\n }\n\n const patchNavigator = (name, value) =>\n utils.replaceProperty(Object.getPrototypeOf(navigator), name, {\n get() {\n return value\n }\n })\n\n patchNavigator('mimeTypes', mimeTypes)\n patchNavigator('plugins', plugins)\n\n // All done\n }",_args:[{fns:{generateMimeTypeArray:"(utils, fns) => mimeTypesData => {\n return fns.generateMagicArray(utils, fns)(\n mimeTypesData,\n MimeTypeArray.prototype,\n MimeType.prototype,\n 'type'\n )\n}",generatePluginArray:"(utils, fns) => pluginsData => {\n return fns.generateMagicArray(utils, fns)(\n pluginsData,\n PluginArray.prototype,\n Plugin.prototype,\n 'name'\n )\n}",generateMagicArray:"(utils, fns) =>\n function(\n dataArray = [],\n proto = MimeTypeArray.prototype,\n itemProto = MimeType.prototype,\n itemMainProp = 'type'\n ) {\n // Quick helper to set props with the same descriptors vanilla is using\n const defineProp = (obj, prop, value) =>\n Object.defineProperty(obj, prop, {\n value,\n writable: false,\n enumerable: false, // Important for mimeTypes & plugins: ` + "`" + `JSON.stringify(navigator.mimeTypes)` + "`" + `\n configurable: true\n })\n\n // Loop over our fake data and construct items\n const makeItem = data => {\n const item = {}\n for (const prop of Object.keys(data)) {\n if (prop.startsWith('__')) {\n continue\n }\n defineProp(item, prop, data[prop])\n }\n return patchItem(item, data)\n }\n\n const patchItem = (item, data) => {\n let descriptor = Object.getOwnPropertyDescriptors(item)\n\n // Special case: Plugins have a magic length property which is not enumerable\n // e.g. ` + "`" + `navigator.plugins[i].length` + "`" + ` should always be the length of the assigned mimeTypes\n if (itemProto === Plugin.prototype) {\n descriptor = {\n ...descriptor,\n length: {\n value: data.__mimeTypes.length,\n writable: false,\n enumerable: false,\n configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip ` + "`" + `length` + "`" + `\n }\n }\n }\n\n // We need to spoof a specific ` + "`" + `MimeType` + "`" + ` or ` + "`" + `Plugin` + "`" + ` object\n const obj = Object.create(itemProto, descriptor)\n\n // Virtually all property keys are not enumerable in vanilla\n const blacklist = [...Object.keys(data), 'length', 'enabledPlugin']\n return new Proxy(obj, {\n ownKeys(target) {\n return Reflect.ownKeys(target).filter(k => !blacklist.includes(k))\n },\n getOwnPropertyDescriptor(target, prop) {\n if (blacklist.includes(prop)) {\n return undefined\n }\n return Reflect.getOwnPropertyDescriptor(target, prop)\n }\n })\n }\n\n const magicArray = []\n\n // Loop through our fake data and use that to create convincing entities\n dataArray.forEach(data => {\n magicArray.push(makeItem(data))\n })\n\n // Add direct property access based on types (e.g. ` + "`" + `obj['application/pdf']` + "`" + `) afterwards\n magicArray.forEach(entry => {\n defineProp(magicArray, entry[itemMainProp], entry)\n })\n\n // This is the best way to fake the type to make sure this is false: ` + "`" + `Array.isArray(navigator.mimeTypes)` + "`" + `\n const magicArrayObj = Object.create(proto, {\n ...Object.getOwnPropertyDescriptors(magicArray),\n\n // There's one ugly quirk we unfortunately need to take care of:\n // The ` + "`" + `MimeTypeArray` + "`" + ` prototype has an enumerable ` + "`" + `length` + "`" + ` property,\n // but headful Chrome will still skip it when running ` + "`" + `Object.getOwnPropertyNames(navigator.mimeTypes)` + "`" + `.\n // To strip it we need to make it first ` + "`" + `configurable` + "`" + ` and can then overlay a Proxy with an ` + "`" + `ownKeys` + "`" + ` trap.\n length: {\n value: magicArray.length,\n writable: false,\n enumerable: false,\n configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip ` + "`" + `length` + "`" + `\n }\n })\n\n // Generate our functional function mocks :-)\n const functionMocks = fns.generateFunctionMocks(utils)(\n proto,\n itemMainProp,\n magicArray\n )\n\n // We need to overlay our custom object with a JS Proxy\n const magicArrayObjProxy = new Proxy(magicArrayObj, {\n get(target, key = '') {\n // Redirect function calls to our custom proxied versions mocking the vanilla behavior\n if (key === 'item') {\n return functionMocks.item\n }\n if (key === 'namedItem') {\n return functionMocks.namedItem\n }\n if (proto === PluginArray.prototype && key === 'refresh') {\n return functionMocks.refresh\n }\n // Everything else can pass through as normal\n return utils.cache.Reflect.get(...arguments)\n },\n ownKeys(target) {\n // There are a couple of quirks where the original property demonstrates \"magical\" behavior that makes no sense\n // This can be witnessed when calling ` + "`" + `Object.getOwnPropertyNames(navigator.mimeTypes)` + "`" + ` and the absence of ` + "`" + `length` + "`" + `\n // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly\n // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing\n // Specific issues when not patching this: ` + "`" + `length` + "`" + ` property is available, direct ` + "`" + `types` + "`" + ` props (e.g. ` + "`" + `obj['application/pdf']` + "`" + `) are missing\n const keys = []\n const typeProps = magicArray.map(mt => mt[itemMainProp])\n typeProps.forEach((_, i) => keys.push(` + "`" + `${i}` + "`" + `))\n typeProps.forEach(propName => keys.push(propName))\n return keys\n },\n getOwnPropertyDescriptor(target, prop) {\n if (prop === 'length') {\n return undefined\n }\n return Reflect.getOwnPropertyDescriptor(target, prop)\n }\n })\n\n return magicArrayObjProxy\n }",generateFunctionMocks:"utils => (\n proto,\n itemMainProp,\n dataArray\n) => ({\n /** Returns the MimeType object with the specified index. */\n item: utils.createProxy(proto.item, {\n apply(target, ctx, args) {\n if (!args.length) {\n throw new TypeError(\n ` + "`" + `Failed to execute 'item' on '${\n proto[Symbol.toStringTag]\n }': 1 argument required, but only 0 present.` + "`" + `\n )\n }\n // Special behavior alert:\n // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup\n // - If anything else than an integer (including as string) is provided it will return the first entry\n const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integer\n // Note: Vanilla never returns ` + "`" + `undefined` + "`" + `\n return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null\n }\n }),\n /** Returns the MimeType object with the specified name. */\n namedItem: utils.createProxy(proto.namedItem, {\n apply(target, ctx, args) {\n if (!args.length) {\n throw new TypeError(\n ` + "`" + `Failed to execute 'namedItem' on '${\n proto[Symbol.toStringTag]\n }': 1 argument required, but only 0 present.` + "`" + `\n )\n }\n return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not ` + "`" + `undefined` + "`" + `!\n }\n }),\n /** Does nothing and shall return nothing */\n refresh: proto.refresh\n ? utils.createProxy(proto.refresh, {\n apply(target, ctx, args) {\n return undefined\n }\n })\n : undefined\n})"},data:{mimeTypes:[{type:"application/pdf",suffixes:"pdf",description:"",__pluginName:"Chrome PDF Viewer"},{type:"application/x-google-chrome-pdf",suffixes:"pdf",description:"Portable Document Format",__pluginName:"Chrome PDF Plugin"},{type:"application/x-nacl",suffixes:"",description:"Native Client Executable",__pluginName:"Native Client"},{type:"application/x-pnacl",suffixes:"",description:"Portable Native Client Executable",__pluginName:"Native Client"}],plugins:[{name:"Chrome PDF Plugin",filename:"internal-pdf-viewer",description:"Portable Document Format",__mimeTypes:["application/x-google-chrome-pdf"]},{name:"Chrome PDF Viewer",filename:"mhjfbmdgcfjbbpaeojofohoefgiehjai",description:"",__mimeTypes:["application/pdf"]},{name:"Native Client",filename:"internal-nacl-plugin",description:"",__mimeTypes:["application/x-nacl","application/x-pnacl"]}]}}]}),!1===navigator.webdriver||void 0===navigator.webdriver||delete Object.getPrototypeOf(navigator).webdriver,(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"(utils, opts) => {\n const getParameterProxyHandler = {\n apply: function(target, ctx, args) {\n const param = (args || [])[0]\n const result = utils.cache.Reflect.apply(target, ctx, args)\n // UNMASKED_VENDOR_WEBGL\n if (param === 37445) {\n return opts.vendor || 'Intel Inc.' // default in headless: Google Inc.\n }\n // UNMASKED_RENDERER_WEBGL\n if (param === 37446) {\n return opts.renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShader\n }\n return result\n }\n }\n\n // There's more than one WebGL rendering context\n // https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibility\n // To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter)\n const addProxy = (obj, propName) => {\n utils.replaceWithProxy(obj, propName, getParameterProxyHandler)\n }\n // For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing:\n addProxy(WebGLRenderingContext.prototype, 'getParameter')\n addProxy(WebGL2RenderingContext.prototype, 'getParameter')\n }",_args:[{}]}),(()=>{try{if(window.outerWidth&&window.outerHeight)return;const n=85;window.outerWidth=window.innerWidth,window.outerHeight=window.innerHeight+n}catch(n){}})(),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n const newHandler = {\n setPrototypeOf: function (target, proto) {\n if (proto === null)\n throw new TypeError('Cannot convert object to primitive value')\n if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n throw new TypeError('Cyclic __proto__ value')\n }\n return Reflect.setPrototypeOf(target, proto)\n }\n }\n // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n const traps = Object.getOwnPropertyNames(handler)\n traps.forEach(trap => {\n newHandler[trap] = function () {\n try {\n // Forward the call to the defined proxy handler\n return handler[trap].apply(this, arguments || [])\n } catch (err) {\n // Stack traces differ per browser, we only support chromium based ones currently\n if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n throw err\n }\n\n // When something throws within one of our traps the Proxy will show up in error stacks\n // An earlier implementation of this code would simply strip lines with a blacklist,\n // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n // We try to use a known \"anchor\" line for that and strip it with everything above it.\n // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n const stripWithBlacklist = (stack, stripFirstLine = true) => {\n const blacklist = [\n ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n ]\n return (\n err.stack\n .split('\\n')\n // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n .filter((line, index) => !(index === 1 && stripFirstLine))\n // Check if the line starts with one of our blacklisted strings\n .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n .join('\\n')\n )\n }\n\n const stripWithAnchor = (stack, anchor) => {\n const stackArr = stack.split('\\n')\n anchor = anchor || ` + "`" + `at Object.newHandler. [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n const anchorIndex = stackArr.findIndex(line =>\n line.trim().startsWith(anchor)\n )\n if (anchorIndex === -1) {\n return false // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n return stackArr.join('\\n')\n }\n\n // Special cases due to our nested toString proxies\n err.stack = err.stack.replace(\n 'at Object.toString (',\n 'at Function.toString ('\n )\n if ((err.stack || '').includes('at Function.toString (')) {\n err.stack = stripWithBlacklist(err.stack, false)\n throw err\n }\n\n // Try using the anchor method, fallback to blacklist if necessary\n err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n throw err // Re-throw our now sanitized error\n }\n }\n })\n return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n const stackArr = err.stack.split('\\n')\n const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n if (anchorIndex === -1) {\n return err // 404, anchor not found\n }\n // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n stackArr.splice(1, anchorIndex)\n err.stack = stackArr.join('\\n')\n return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n return Object.defineProperty(obj, propName, {\n // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n // Add our overrides (e.g. value, get())\n ...descriptorOverrides\n })\n}",preloadCache:"() => {\n if (utils.cache) {\n return\n }\n utils.cache = {\n // Used in our proxies\n Reflect: {\n get: Reflect.get.bind(Reflect),\n apply: Reflect.apply.bind(Reflect)\n },\n // Used in ` + "`" + `makeNativeString` + "`" + `\n nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n }\n}",makeNativeString:"(name = '') => {\n return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === obj) {\n // We either return the optional string verbatim or derive the most desired result automatically\n return str || utils.makeNativeString(obj.name)\n }\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",patchToStringNested:"(obj = {}) => {\n return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n const handler = {\n apply: function (target, ctx) {\n // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n if (ctx === Function.prototype.toString) {\n return utils.makeNativeString('toString')\n }\n\n // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n if (ctx === proxyObj) {\n const fallback = () =>\n originalObj && originalObj.name\n ? utils.makeNativeString(originalObj.name)\n : utils.makeNativeString(proxyObj.name)\n\n // Return the toString representation of our original object if possible\n return originalObj + '' || fallback()\n }\n\n if (typeof ctx === 'undefined' || ctx === null) {\n return target.call(ctx)\n }\n\n // Check if the toString protype of the context is the same as the global prototype,\n // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n const hasSameProto = Object.getPrototypeOf(\n Function.prototype.toString\n ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n if (!hasSameProto) {\n // Pass the call on to the local Function.prototype.toString instead\n return ctx.toString()\n }\n\n return target.call(ctx)\n }\n }\n\n const toStringProxy = new Proxy(\n Function.prototype.toString,\n utils.stripProxyFromErrors(handler)\n )\n utils.replaceProperty(Function.prototype, 'toString', {\n value: toStringProxy\n })\n}",replaceWithProxy:"(obj, propName, handler) => {\n const originalObj = obj[propName]\n const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.redirectToString(proxyObj, originalObj)\n\n return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n const fnStr = fn.toString() // special getter function string\n const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { get: proxyObj })\n utils.patchToString(proxyObj, fnStr)\n\n return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n const handler = { ...ownPropertyDescriptor }\n\n if (handlerGetterSetter.get !== undefined) {\n const nativeFn = ownPropertyDescriptor.get\n handler.get = function() {\n return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n }\n utils.redirectToString(handler.get, nativeFn)\n }\n\n if (handlerGetterSetter.set !== undefined) {\n const nativeFn = ownPropertyDescriptor.set\n handler.set = function(newValue) {\n handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n }\n utils.redirectToString(handler.set, nativeFn)\n }\n\n Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n utils.replaceProperty(obj, propName, { value: proxyObj })\n utils.patchToString(proxyObj)\n\n return true\n}",createProxy:"(pseudoTarget, handler) => {\n const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n utils.patchToString(proxyObj)\n\n return proxyObj\n}",splitObjPath:"objPath => ({\n // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n objName: objPath.split('.').slice(0, -1).join('.'),\n // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n const { objName, propName } = utils.splitObjPath(objPath)\n const obj = eval(objName) // eslint-disable-line no-eval\n return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n function recurse(obj) {\n for (const key in obj) {\n if (obj[key] === undefined) {\n continue\n }\n if (obj[key] && typeof obj[key] === 'object') {\n recurse(obj[key])\n } else {\n if (obj[key] && typeFilter.includes(typeof obj[key])) {\n fn.call(this, obj[key])\n }\n }\n }\n }\n recurse(obj)\n return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n // https://github.com/feross/fromentries\n function fromEntries(iterable) {\n return [...iterable].reduce((obj, [key, val]) => {\n obj[key] = val\n return obj\n }, {})\n }\n return (Object.fromEntries || fromEntries)(\n Object.entries(fnObj)\n .filter(([key, value]) => typeof value === 'function')\n .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n return Object.fromEntries(\n Object.entries(fnStrObj).map(([key, value]) => {\n if (value.startsWith('function')) {\n // some trickery is needed to make oldschool functions work :-)\n return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n } else {\n // arrow functions just work\n return [key, eval(value)] // eslint-disable-line no-eval\n }\n })\n )\n}",makeHandler:"() => ({\n // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n getterValue: value => ({\n apply(target, ctx, args) {\n // Let's fetch the value first, to trigger and escalate potential errors\n // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n utils.cache.Reflect.apply(...arguments)\n return value\n }\n })\n})",arrayEquals:"(array1, array2) => {\n if (array1.length !== array2.length) {\n return false\n }\n for (let i = 0; i < array1.length; ++i) {\n if (array1[i] !== array2[i]) {\n return false\n }\n }\n return true\n}",memoize:"fn => {\n const cache = []\n return function(...args) {\n if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n cache.push({ key: args, value: fn.apply(this, args) })\n }\n return cache.find(c => utils.arrayEquals(c.key, args)).value\n }\n}"},_mainFunction:"(utils, opts) => {\n try {\n // Adds a contentWindow proxy to the provided iframe element\n const addContentWindowProxy = iframe => {\n const contentWindowProxy = {\n get(target, key) {\n // Now to the interesting part:\n // We actually make this thing behave like a regular iframe window,\n // by intercepting calls to e.g. ` + "`" + `.self` + "`" + ` and redirect it to the correct thing. :)\n // That makes it possible for these assertions to be correct:\n // iframe.contentWindow.self === window.top // must be false\n if (key === 'self') {\n return this\n }\n // iframe.contentWindow.frameElement === iframe // must be true\n if (key === 'frameElement') {\n return iframe\n }\n // Intercept iframe.contentWindow[0] to hide the property 0 added by the proxy.\n if (key === '0') {\n return undefined\n }\n return Reflect.get(target, key)\n }\n }\n\n if (!iframe.contentWindow) {\n const proxy = new Proxy(window, contentWindowProxy)\n Object.defineProperty(iframe, 'contentWindow', {\n get() {\n return proxy\n },\n set(newValue) {\n return newValue // contentWindow is immutable\n },\n enumerable: true,\n configurable: false\n })\n }\n }\n\n // Handles iframe element creation, augments ` + "`" + `srcdoc` + "`" + ` property so we can intercept further\n const handleIframeCreation = (target, thisArg, args) => {\n const iframe = target.apply(thisArg, args)\n\n // We need to keep the originals around\n const _iframe = iframe\n const _srcdoc = _iframe.srcdoc\n\n // Add hook for the srcdoc property\n // We need to be very surgical here to not break other iframes by accident\n Object.defineProperty(iframe, 'srcdoc', {\n configurable: true, // Important, so we can reset this later\n get: function() {\n return _srcdoc\n },\n set: function(newValue) {\n addContentWindowProxy(this)\n // Reset property, the hook is only needed once\n Object.defineProperty(iframe, 'srcdoc', {\n configurable: false,\n writable: false,\n value: _srcdoc\n })\n _iframe.srcdoc = newValue\n }\n })\n return iframe\n }\n\n // Adds a hook to intercept iframe creation events\n const addIframeCreationSniffer = () => {\n /* global document */\n const createElementHandler = {\n // Make toString() native\n get(target, key) {\n return Reflect.get(target, key)\n },\n apply: function(target, thisArg, args) {\n const isIframe =\n args && args.length && ` + "`" + `${args[0]}` + "`" + `.toLowerCase() === 'iframe'\n if (!isIframe) {\n // Everything as usual\n return target.apply(thisArg, args)\n } else {\n return handleIframeCreation(target, thisArg, args)\n }\n }\n }\n // All this just due to iframes with srcdoc bug\n utils.replaceWithProxy(\n document,\n 'createElement',\n createElementHandler\n )\n }\n\n // Let's go\n addIframeCreationSniffer()\n } catch (err) {\n // console.warn(err)\n }\n }",_args:[]});
})();`
================================================
FILE: pkg/engine/headless/captcha/capsolver/capsolver.go
================================================
package capsolver
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/projectdiscovery/katana/pkg/engine/headless/captcha"
)
const (
defaultPollInterval = 3 * time.Second
defaultTimeout = 120 * time.Second
)
var baseURL = "https://api.capsolver.com"
func SetBaseURL(url string) {
baseURL = url
}
var taskTypes = map[captcha.Provider]string{
captcha.ProviderRecaptchaV2: "ReCaptchaV2TaskProxyLess",
captcha.ProviderRecaptchaV3: "ReCaptchaV3TaskProxyLess",
captcha.ProviderRecaptchaV2Enterprise: "ReCaptchaV2EnterpriseTaskProxyLess",
captcha.ProviderRecaptchaV3Enterprise: "ReCaptchaV3EnterpriseTaskProxyLess",
captcha.ProviderTurnstile: "AntiTurnstileTaskProxyLess",
captcha.ProviderHCaptcha: "HCaptchaTaskProxyLess",
}
type Solver struct {
apiKey string
client *http.Client
}
func init() {
captcha.RegisterSolver("capsolver", func(apiKey string) (captcha.Solver, error) {
return New(apiKey), nil
})
}
func New(apiKey string) *Solver {
return &Solver{
apiKey: apiKey,
client: &http.Client{Timeout: 30 * time.Second},
}
}
func (s *Solver) Solve(ctx context.Context, info *captcha.Info) (*captcha.Solution, error) {
taskType, ok := taskTypes[info.Provider]
if !ok {
return nil, fmt.Errorf("unsupported captcha provider for capsolver: %s", info.Provider)
}
task := map[string]any{
"type": taskType,
"websiteURL": info.PageURL,
"websiteKey": info.SiteKey,
}
if (info.Provider == captcha.ProviderRecaptchaV3 || info.Provider == captcha.ProviderRecaptchaV3Enterprise) && info.Action != "" {
task["pageAction"] = info.Action
}
taskID, err := s.createTask(ctx, task)
if err != nil {
return nil, fmt.Errorf("create task: %w", err)
}
return s.pollResult(ctx, taskID, info.Provider)
}
type createTaskRequest struct {
ClientKey string `json:"clientKey"`
Task map[string]any `json:"task"`
}
type createTaskResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
TaskID string `json:"taskId"`
}
type getTaskResultRequest struct {
ClientKey string `json:"clientKey"`
TaskID string `json:"taskId"`
}
type getTaskResultResponse struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
Status string `json:"status"`
Solution map[string]any `json:"solution"`
}
func (s *Solver) createTask(ctx context.Context, task map[string]any) (string, error) {
body, err := json.Marshal(createTaskRequest{
ClientKey: s.apiKey,
Task: task,
})
if err != nil {
return "", err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/createTask", bytes.NewReader(body))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(req)
if err != nil {
return "", err
}
defer func() { _ = resp.Body.Close() }()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var result createTaskResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return "", err
}
if result.ErrorID != 0 {
return "", fmt.Errorf("capsolver error %s: %s", result.ErrorCode, result.ErrorDescription)
}
return result.TaskID, nil
}
func (s *Solver) pollResult(ctx context.Context, taskID string, provider captcha.Provider) (*captcha.Solution, error) {
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
ticker := time.NewTicker(defaultPollInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, fmt.Errorf("captcha solve timed out after %s", defaultTimeout)
case <-ticker.C:
result, err := s.getTaskResult(ctx, taskID)
if err != nil {
return nil, err
}
if result.ErrorID != 0 {
return nil, fmt.Errorf("capsolver error %s: %s", result.ErrorCode, result.ErrorDescription)
}
if result.Status != "ready" {
continue
}
return extractToken(result.Solution, provider)
}
}
}
func (s *Solver) getTaskResult(ctx context.Context, taskID string) (*getTaskResultResponse, error) {
body, err := json.Marshal(getTaskResultRequest{
ClientKey: s.apiKey,
TaskID: taskID,
})
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/getTaskResult", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := s.client.Do(req)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result getTaskResultResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, err
}
return &result, nil
}
func extractToken(solution map[string]any, provider captcha.Provider) (*captcha.Solution, error) {
// capsolver returns turnstile tokens under "token", everything else under "gRecaptchaResponse"
key := "gRecaptchaResponse"
if provider == captcha.ProviderTurnstile {
key = "token"
}
token, ok := solution[key].(string)
if !ok || token == "" {
return nil, fmt.Errorf("no token found in capsolver solution (key=%s)", key)
}
return &captcha.Solution{Token: token, Provider: provider}, nil
}
================================================
FILE: pkg/engine/headless/captcha/capsolver/capsolver_test.go
================================================
package capsolver
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"github.com/projectdiscovery/katana/pkg/engine/headless/captcha"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSolve(t *testing.T) {
var pollCount atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]any
_ = json.NewDecoder(r.Body).Decode(&body)
switch r.URL.Path {
case "/createTask":
task, ok := body["task"].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "ReCaptchaV2TaskProxyLess", task["type"])
assert.Equal(t, "https://example.com", task["websiteURL"])
assert.Equal(t, "test-sitekey", task["websiteKey"])
_ = json.NewEncoder(w).Encode(map[string]any{
"errorId": 0,
"taskId": "task-123",
})
case "/getTaskResult":
assert.Equal(t, "task-123", body["taskId"])
count := pollCount.Add(1)
if count < 2 {
_ = json.NewEncoder(w).Encode(map[string]any{
"errorId": 0,
"status": "processing",
})
return
}
_ = json.NewEncoder(w).Encode(map[string]any{
"errorId": 0,
"status": "ready",
"solution": map[string]any{
"gRecaptchaResponse": "solved-token-abc",
},
})
}
}))
defer server.Close()
origURL := baseURL
SetBaseURL(server.URL)
defer SetBaseURL(origURL)
cs := New("test-api-key")
solution, err := cs.Solve(context.Background(), &captcha.Info{
Provider: captcha.ProviderRecaptchaV2,
SiteKey: "test-sitekey",
PageURL: "https://example.com",
})
require.NoError(t, err)
require.NotNil(t, solution)
assert.Equal(t, "solved-token-abc", solution.Token)
assert.Equal(t, captcha.ProviderRecaptchaV2, solution.Provider)
assert.GreaterOrEqual(t, int(pollCount.Load()), 2)
}
func TestCreateTaskError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"errorId": 1,
"errorCode": "ERROR_KEY_DENIED",
"errorDescription": "Account not found or blocked",
})
}))
defer server.Close()
origURL := baseURL
SetBaseURL(server.URL)
defer SetBaseURL(origURL)
cs := New("bad-key")
_, err := cs.Solve(context.Background(), &captcha.Info{
Provider: captcha.ProviderRecaptchaV2,
SiteKey: "key",
PageURL: "https://example.com",
})
require.Error(t, err)
assert.Contains(t, err.Error(), "ERROR_KEY_DENIED")
}
func TestTurnstileToken(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/createTask":
_ = json.NewEncoder(w).Encode(map[string]any{
"errorId": 0,
"taskId": "task-turnstile",
})
case "/getTaskResult":
_ = json.NewEncoder(w).Encode(map[string]any{
"errorId": 0,
"status": "ready",
"solution": map[string]any{
"token": "turnstile-token-xyz",
},
})
}
}))
defer server.Close()
origURL := baseURL
SetBaseURL(server.URL)
defer SetBaseURL(origURL)
cs := New("key")
solution, err := cs.Solve(context.Background(), &captcha.Info{
Provider: captcha.ProviderTurnstile,
SiteKey: "cf-key",
PageURL: "https://example.com",
})
require.NoError(t, err)
assert.Equal(t, "turnstile-token-xyz", solution.Token)
assert.Equal(t, captcha.ProviderTurnstile, solution.Provider)
}
func TestExtractToken(t *testing.T) {
tests := []struct {
name string
solution map[string]any
provider captcha.Provider
want string
wantErr bool
}{
{
name: "recaptcha v2",
solution: map[string]any{"gRecaptchaResponse": "token-a"},
provider: captcha.ProviderRecaptchaV2,
want: "token-a",
},
{
name: "turnstile",
solution: map[string]any{"token": "token-b"},
provider: captcha.ProviderTurnstile,
want: "token-b",
},
{
name: "missing token",
solution: map[string]any{},
provider: captcha.ProviderRecaptchaV2,
wantErr: true,
},
{
name: "empty token",
solution: map[string]any{"gRecaptchaResponse": ""},
provider: captcha.ProviderHCaptcha,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sol, err := extractToken(tt.solution, tt.provider)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, sol.Token)
})
}
}
func TestUnsupportedProvider(t *testing.T) {
cs := New("key")
_, err := cs.Solve(context.Background(), &captcha.Info{
Provider: "unknown",
SiteKey: "key",
PageURL: "https://example.com",
})
require.Error(t, err)
assert.Contains(t, err.Error(), "unsupported")
}
================================================
FILE: pkg/engine/headless/captcha/captcha.go
================================================
package captcha
import (
"context"
"fmt"
"strings"
"github.com/go-rod/rod"
ditcaptcha "github.com/happyhackingspace/dit/captcha"
"github.com/projectdiscovery/gologger"
captchajs "github.com/projectdiscovery/katana/pkg/engine/headless/captcha/js"
)
type Handler struct {
solver Solver
}
func NewHandler(solverProvider, apiKey string) (*Handler, error) {
solver, err := NewSolver(solverProvider, apiKey)
if err != nil {
return nil, fmt.Errorf("captcha solver init: %w", err)
}
return &Handler{
solver: solver,
}, nil
}
func (h *Handler) HandleIfCaptcha(ctx context.Context, page *rod.Page, pageHTML string) (bool, error) {
if ditcaptcha.DetectCaptchaInHTML(pageHTML) == ditcaptcha.CaptchaTypeNone && !strings.Contains(pageHTML, "data-sitekey") {
return false, nil
}
info, err := Identify(page)
if err != nil {
gologger.Debug().Msgf("captcha identification failed: %s", err)
}
if info == nil {
return false, nil
}
return h.solveCaptcha(ctx, page, info)
}
func (h *Handler) solveCaptcha(ctx context.Context, page *rod.Page, info *Info) (bool, error) {
gologger.Debug().Msgf("captcha detected: provider=%s sitekey=%s url=%s", info.Provider, info.SiteKey, info.PageURL)
solution, err := h.solver.Solve(ctx, info)
if err != nil {
return true, fmt.Errorf("captcha solve: %w", err)
}
gologger.Debug().Msgf("captcha solved, injecting token: provider=%s", solution.Provider)
if err := injectToken(page, solution); err != nil {
return true, fmt.Errorf("captcha inject: %w", err)
}
return true, nil
}
func injectToken(page *rod.Page, solution *Solution) error {
js, err := injectionScript(solution.Provider)
if err != nil {
return err
}
_, err = page.Eval(js, solution.Token)
return err
}
func injectionScript(provider Provider) (string, error) {
switch provider {
case ProviderRecaptchaV2, ProviderRecaptchaV3, ProviderRecaptchaV2Enterprise, ProviderRecaptchaV3Enterprise:
return captchajs.InjectRecaptchaJS, nil
case ProviderTurnstile:
return captchajs.InjectTurnstileJS, nil
case ProviderHCaptcha:
return captchajs.InjectHCaptchaJS, nil
default:
return "", fmt.Errorf("unsupported captcha provider for injection: %s", provider)
}
}
================================================
FILE: pkg/engine/headless/captcha/helpers_test.go
================================================
package captcha
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
)
func setupBrowser(t *testing.T) *rod.Browser {
t.Helper()
path, found := launcher.LookPath()
if !found {
t.Skip("chromium not found, skipping browser tests")
}
u := launcher.New().Bin(path).Headless(true).Leakless(true).MustLaunch()
browser := rod.New().ControlURL(u).MustConnect()
t.Cleanup(func() { browser.MustClose() })
return browser
}
func servePage(t *testing.T, html string) string {
t.Helper()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
_, _ = fmt.Fprint(w, html)
}))
t.Cleanup(server.Close)
return server.URL
}
================================================
FILE: pkg/engine/headless/captcha/identify.go
================================================
package captcha
import (
"github.com/go-rod/rod"
captchajs "github.com/projectdiscovery/katana/pkg/engine/headless/captcha/js"
)
type Provider string
const (
ProviderRecaptchaV2 Provider = "recaptchav2"
ProviderRecaptchaV3 Provider = "recaptchav3"
ProviderRecaptchaV2Enterprise Provider = "recaptchav2enterprise"
ProviderRecaptchaV3Enterprise Provider = "recaptchav3enterprise"
ProviderTurnstile Provider = "turnstile"
ProviderHCaptcha Provider = "hcaptcha"
)
type Info struct {
Provider Provider
SiteKey string
PageURL string
Action string
}
func Identify(page *rod.Page) (*Info, error) {
pageURL, err := page.Eval("() => window.location.href")
if err != nil {
return nil, err
}
result, err := page.Eval(captchajs.IdentifyJS)
if err != nil {
return nil, err
}
if result.Value.Nil() {
return nil, nil
}
return &Info{
Provider: Provider(result.Value.Get("provider").Str()),
SiteKey: result.Value.Get("sitekey").Str(),
PageURL: pageURL.Value.Str(),
Action: result.Value.Get("action").Str(),
}, nil
}
================================================
FILE: pkg/engine/headless/captcha/identify_test.go
================================================
package captcha
import (
"testing"
ditcaptcha "github.com/happyhackingspace/dit/captcha"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDetectCaptchaWithDit(t *testing.T) {
tests := []struct {
name string
html string
want ditcaptcha.CaptchaType
}{
{
name: "recaptcha",
html: `
`,
want: ditcaptcha.CaptchaTypeRecaptcha,
},
{
name: "turnstile",
html: `
`,
want: ditcaptcha.CaptchaTypeTurnstile,
},
{
name: "hcaptcha",
html: `
`,
want: ditcaptcha.CaptchaTypeHCaptcha,
},
{
name: "no captcha",
html: `Hello `,
want: ditcaptcha.CaptchaTypeNone,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ditcaptcha.DetectCaptchaInHTML(tt.html)
assert.Equal(t, tt.want, got)
})
}
}
func TestIdentify(t *testing.T) {
browser := setupBrowser(t)
tests := []struct {
name string
html string
wantProvider Provider
wantSiteKey string
wantNil bool
}{
{
name: "recaptcha v2",
html: `
`,
wantProvider: ProviderRecaptchaV2,
wantSiteKey: "6LcXrecapv2",
},
{
name: "recaptcha v3",
html: `
`,
wantProvider: ProviderRecaptchaV3,
wantSiteKey: "6LcXrecapv3",
},
{
name: "cloudflare turnstile",
html: `
`,
wantProvider: ProviderTurnstile,
wantSiteKey: "0x4AAATURNSTILE",
},
{
name: "hcaptcha",
html: `
`,
wantProvider: ProviderHCaptcha,
wantSiteKey: "hcap-sitekey-123",
},
{
name: "no captcha",
html: `
Hello World
`,
wantNil: true,
},
{
name: "recaptcha v2 enterprise",
html: `
`,
wantProvider: ProviderRecaptchaV2Enterprise,
wantSiteKey: "6LcEntV2",
},
{
name: "recaptcha v3 enterprise",
html: `
`,
wantProvider: ProviderRecaptchaV3Enterprise,
wantSiteKey: "6LcEntV3",
},
{
name: "generic data-sitekey fallback",
html: `
`,
wantProvider: ProviderRecaptchaV2,
wantSiteKey: "generic-key-456",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
url := servePage(t, tt.html)
page := browser.MustPage(url)
defer page.MustClose()
page.MustWaitLoad()
info, err := Identify(page)
require.NoError(t, err)
if tt.wantNil {
assert.Nil(t, info)
return
}
require.NotNil(t, info)
assert.Equal(t, tt.wantProvider, info.Provider)
assert.Equal(t, tt.wantSiteKey, info.SiteKey)
assert.Contains(t, info.PageURL, url)
})
}
}
================================================
FILE: pkg/engine/headless/captcha/inject_test.go
================================================
package captcha
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInjectionScript(t *testing.T) {
tests := []struct {
name string
provider Provider
wantErr bool
contains string
}{
{
name: "recaptcha v2",
provider: ProviderRecaptchaV2,
contains: "g-recaptcha-response",
},
{
name: "recaptcha v3",
provider: ProviderRecaptchaV3,
contains: "g-recaptcha-response",
},
{
name: "recaptcha v2 enterprise",
provider: ProviderRecaptchaV2Enterprise,
contains: "g-recaptcha-response",
},
{
name: "recaptcha v3 enterprise",
provider: ProviderRecaptchaV3Enterprise,
contains: "g-recaptcha-response",
},
{
name: "turnstile",
provider: ProviderTurnstile,
contains: "cf-turnstile-response",
},
{
name: "hcaptcha",
provider: ProviderHCaptcha,
contains: "h-captcha-response",
},
{
name: "unknown provider",
provider: "unknown",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
js, err := injectionScript(tt.provider)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Contains(t, js, tt.contains)
})
}
}
================================================
FILE: pkg/engine/headless/captcha/injection_test.go
================================================
package captcha
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestE2E_RecaptchaV2_Injection(t *testing.T) {
browser := setupBrowser(t)
received := make(chan string, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
_ = r.ParseForm()
received <- r.FormValue("g-recaptcha-response")
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(`OK
`))
return
}
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(`
`))
}))
t.Cleanup(server.Close)
p := browser.MustPage(server.URL)
defer p.MustClose()
p.MustWaitLoad()
info, err := Identify(p)
require.NoError(t, err)
require.NotNil(t, info)
assert.Equal(t, ProviderRecaptchaV2, info.Provider)
assert.Equal(t, "6LcTestKey", info.SiteKey)
err = injectToken(p, &Solution{Token: "test-token-abc", Provider: ProviderRecaptchaV2})
require.NoError(t, err)
p.MustWaitLoad()
token := <-received
assert.Equal(t, "test-token-abc", token)
}
func TestE2E_Turnstile_Injection(t *testing.T) {
browser := setupBrowser(t)
received := make(chan string, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
_ = r.ParseForm()
received <- r.FormValue("cf-turnstile-response")
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(`OK
`))
return
}
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(`
`))
}))
t.Cleanup(server.Close)
p := browser.MustPage(server.URL)
defer p.MustClose()
p.MustWaitLoad()
info, err := Identify(p)
require.NoError(t, err)
require.NotNil(t, info)
assert.Equal(t, ProviderTurnstile, info.Provider)
assert.Equal(t, "0x4AAATurnstileKey", info.SiteKey)
err = injectToken(p, &Solution{Token: "test-token-def", Provider: ProviderTurnstile})
require.NoError(t, err)
p.MustWaitLoad()
token := <-received
assert.Equal(t, "test-token-def", token)
}
func TestE2E_HCaptcha_Injection(t *testing.T) {
browser := setupBrowser(t)
received := make(chan map[string]string, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
_ = r.ParseForm()
received <- map[string]string{
"h-captcha-response": r.FormValue("h-captcha-response"),
"g-recaptcha-response": r.FormValue("g-recaptcha-response"),
}
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(`OK
`))
return
}
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(`
`))
}))
t.Cleanup(server.Close)
p := browser.MustPage(server.URL)
defer p.MustClose()
p.MustWaitLoad()
info, err := Identify(p)
require.NoError(t, err)
require.NotNil(t, info)
assert.Equal(t, ProviderHCaptcha, info.Provider)
assert.Equal(t, "hcap-test-key", info.SiteKey)
err = injectToken(p, &Solution{Token: "test-token-ghi", Provider: ProviderHCaptcha})
require.NoError(t, err)
p.MustWaitLoad()
vals := <-received
assert.Equal(t, "test-token-ghi", vals["h-captcha-response"])
assert.Equal(t, "test-token-ghi", vals["g-recaptcha-response"])
}
func TestE2E_RecaptchaV2_FormSubmitFallback(t *testing.T) {
browser := setupBrowser(t)
received := make(chan string, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
_ = r.ParseForm()
received <- r.FormValue("g-recaptcha-response")
_, _ = w.Write([]byte("OK"))
return
}
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(`
`))
}))
t.Cleanup(server.Close)
p := browser.MustPage(server.URL)
defer p.MustClose()
p.MustWaitLoad()
err := injectToken(p, &Solution{Token: "fallback-token", Provider: ProviderRecaptchaV2})
require.NoError(t, err)
p.MustWaitLoad()
token := <-received
assert.Equal(t, "fallback-token", token)
}
================================================
FILE: pkg/engine/headless/captcha/integration_test.go
================================================
package captcha
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIntegration_DetectRealCaptchaPages(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
browser := setupBrowser(t)
tests := []struct {
name string
url string
wantProvider Provider
}{
{
name: "google recaptcha v2 demo",
url: "https://www.google.com/recaptcha/api2/demo",
wantProvider: ProviderRecaptchaV2,
},
{
name: "hcaptcha official demo",
url: "https://accounts.hcaptcha.com/demo",
wantProvider: ProviderHCaptcha,
},
{
name: "2captcha turnstile demo",
url: "https://2captcha.com/demo/cloudflare-turnstile",
wantProvider: ProviderTurnstile,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
page := browser.MustPage(tt.url)
defer page.MustClose()
page.MustWaitLoad()
info, err := Identify(page)
require.NoError(t, err)
require.NotNil(t, info, "expected captcha to be detected on %s", tt.url)
t.Logf("detected: provider=%s sitekey=%s url=%s", info.Provider, info.SiteKey, info.PageURL)
assert.Equal(t, tt.wantProvider, info.Provider)
assert.NotEmpty(t, info.SiteKey)
})
}
}
================================================
FILE: pkg/engine/headless/captcha/js/identify.js
================================================
() => {
const hcap = document.querySelector('.h-captcha[data-sitekey]');
if (hcap) {
return { provider: "hcaptcha", sitekey: hcap.getAttribute("data-sitekey"), action: "" };
}
const cf = document.querySelector('.cf-turnstile[data-sitekey]');
if (cf) {
return { provider: "turnstile", sitekey: cf.getAttribute("data-sitekey"), action: "" };
}
const recapScripts = document.querySelectorAll(
'script[src*="recaptcha/api.js"], script[src*="recaptcha/enterprise.js"]'
);
for (const s of recapScripts) {
try {
const u = new URL(s.src);
const isEnterprise = u.pathname.includes("/enterprise.js");
const renderParam = u.searchParams.get("render");
if (renderParam && renderParam !== "explicit") {
return {
provider: isEnterprise ? "recaptchav3enterprise" : "recaptchav3",
sitekey: renderParam,
action: ""
};
}
} catch {}
}
const isEnterprise = recapScripts.length > 0 &&
Array.from(recapScripts).some(s => s.src.includes("/enterprise.js"));
const recap = document.querySelector('.g-recaptcha[data-sitekey]');
if (recap) {
return {
provider: isEnterprise ? "recaptchav2enterprise" : "recaptchav2",
sitekey: recap.getAttribute("data-sitekey"),
action: ""
};
}
const generic = document.querySelector('[data-sitekey]');
if (generic) {
return {
provider: isEnterprise ? "recaptchav2enterprise" : "recaptchav2",
sitekey: generic.getAttribute("data-sitekey"),
action: ""
};
}
return null;
}
================================================
FILE: pkg/engine/headless/captcha/js/inject-hcaptcha.js
================================================
(token) => {
document.querySelectorAll('textarea[name="h-captcha-response"]').forEach(el => { el.value = token; });
document.querySelectorAll('textarea[name="g-recaptcha-response"]').forEach(el => { el.value = token; });
let called = false;
if (!called) {
const el = document.querySelector('.h-captcha[data-callback]');
if (el) {
const name = el.getAttribute('data-callback');
if (name && typeof window[name] === 'function') {
window[name](token);
called = true;
}
}
}
// callback alone may not trigger navigation
const form = document.querySelector('form:has(.h-captcha)') ||
document.querySelector('form:has([name="h-captcha-response"])');
if (form) form.submit();
}
================================================
FILE: pkg/engine/headless/captcha/js/inject-recaptcha.js
================================================
(token) => {
document.querySelectorAll('[id="g-recaptcha-response"], [name="g-recaptcha-response"]').forEach(el => {
el.value = token;
el.style.display = 'block';
});
let called = false;
if (!called) {
const el = document.querySelector('.g-recaptcha[data-callback]');
if (el) {
const name = el.getAttribute('data-callback');
if (name && typeof window[name] === 'function') {
window[name](token);
called = true;
}
}
}
// ___grecaptcha_cfg.clients holds internal reCAPTCHA state including registered callbacks
if (!called && typeof ___grecaptcha_cfg !== 'undefined' && ___grecaptcha_cfg.clients) {
try {
for (const key in ___grecaptcha_cfg.clients) {
const client = ___grecaptcha_cfg.clients[key];
const find = (obj, depth) => {
if (depth > 4 || !obj || typeof obj !== 'object') return null;
if (obj instanceof Node || obj instanceof Window) return null;
try {
for (const k in obj) {
try {
const v = obj[k];
if (k === 'callback' && typeof v === 'function') return v;
if (v && typeof v === 'object' && !(v instanceof Node) && !(v instanceof Window)) {
if (typeof v.callback === 'function') return v.callback;
const found = find(v, depth + 1);
if (found) return found;
}
} catch(e) { continue; }
}
} catch(e) { return null; }
return null;
};
const cb = find(client, 0);
if (cb) { cb(token); called = true; break; }
}
} catch(e) {}
}
// callback alone may not trigger navigation
const form = document.querySelector('form:has(#g-recaptcha-response)') ||
document.querySelector('form:has(.g-recaptcha)');
if (form) form.submit();
}
================================================
FILE: pkg/engine/headless/captcha/js/inject-turnstile.js
================================================
(token) => {
document.querySelectorAll('input[name="cf-turnstile-response"]').forEach(el => { el.value = token; });
let called = false;
if (!called) {
const el = document.querySelector('.cf-turnstile[data-callback]');
if (el) {
const name = el.getAttribute('data-callback');
if (name && typeof window[name] === 'function') {
window[name](token);
called = true;
}
}
}
// callback alone may not trigger navigation
const form = document.querySelector('form:has(.cf-turnstile)') ||
document.querySelector('form:has([name="cf-turnstile-response"])');
if (form) form.submit();
}
================================================
FILE: pkg/engine/headless/captcha/js/js.go
================================================
package js
import _ "embed"
var (
//go:embed identify.js
IdentifyJS string
//go:embed inject-recaptcha.js
InjectRecaptchaJS string
//go:embed inject-turnstile.js
InjectTurnstileJS string
//go:embed inject-hcaptcha.js
InjectHCaptchaJS string
)
================================================
FILE: pkg/engine/headless/captcha/solver.go
================================================
package captcha
import (
"context"
"fmt"
)
type Solution struct {
Token string
Provider Provider
}
type Solver interface {
Solve(ctx context.Context, info *Info) (*Solution, error)
}
func NewSolver(provider, apiKey string) (Solver, error) {
constructor, ok := solverRegistry[provider]
if !ok {
return nil, fmt.Errorf("unsupported captcha solver provider: %s", provider)
}
return constructor(apiKey)
}
type SolverConstructor func(apiKey string) (Solver, error)
var solverRegistry = map[string]SolverConstructor{}
func RegisterSolver(name string, constructor SolverConstructor) {
solverRegistry[name] = constructor
}
================================================
FILE: pkg/engine/headless/captcha/solver_test.go
================================================
package captcha
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewSolver_UnsupportedProvider(t *testing.T) {
_, err := NewSolver("unknown-provider", "key")
require.Error(t, err)
assert.Contains(t, err.Error(), "unsupported")
}
func TestRegisterSolver(t *testing.T) {
RegisterSolver("test-provider", func(apiKey string) (Solver, error) {
return nil, nil
})
defer delete(solverRegistry, "test-provider")
_, ok := solverRegistry["test-provider"]
assert.True(t, ok)
}
================================================
FILE: pkg/engine/headless/crawler/crawler.go
================================================
package crawler
import (
"context"
"fmt"
"log/slog"
"os"
"os/user"
"path/filepath"
"regexp"
"sync"
"time"
"github.com/adrianbrad/queue"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
"github.com/go-rod/rod/lib/utils"
"github.com/pkg/errors"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/engine/headless/browser"
"github.com/projectdiscovery/katana/pkg/engine/headless/captcha"
"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/diagnostics"
"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/normalizer"
"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/normalizer/simhash"
"github.com/projectdiscovery/katana/pkg/engine/headless/graph"
"github.com/projectdiscovery/katana/pkg/engine/headless/types"
"github.com/projectdiscovery/katana/pkg/output"
)
type Crawler struct {
logger *slog.Logger
launcher *browser.Launcher
options Options
crawlQueue queue.Queue[*types.Action]
crawlGraph *graph.CrawlGraph
simhashOracle *simhash.Oracle
uniqueActions map[string]struct{}
diagnostics diagnostics.Writer
}
type Options struct {
ChromiumPath string
MaxBrowsers int
MaxDepth int
PageMaxTimeout time.Duration
NoSandbox bool
ShowBrowser bool
SlowMotion bool
MaxCrawlDuration time.Duration
MaxFailureCount int
Trace bool
CookieConsentBypass bool
AutomaticFormFill bool
// EnableDiagnostics enables the diagnostics mode
// which writes diagnostic information to a directory
// specified by the DiagnosticsDir optionally.
EnableDiagnostics bool
DiagnosticsDir string
Proxy string
Logger *slog.Logger
ScopeValidator browser.ScopeValidator
RequestCallback func(*output.Result)
ChromeUser *user.User
CaptchaHandler *captcha.Handler
}
var domNormalizer *normalizer.Normalizer
var initOnce sync.Once
var initError error
func init() {
initOnce.Do(func() {
var err error
domNormalizer, err = normalizer.New()
if err != nil {
initError = errors.Wrap(err, "failed to create domnormalizer")
}
})
}
func New(opts Options) (*Crawler, error) {
if initError != nil {
return nil, initError
}
if opts.Logger == nil {
opts.Logger = slog.Default()
}
launcher, err := browser.NewLauncher(browser.LauncherOptions{
ChromiumPath: opts.ChromiumPath,
MaxBrowsers: opts.MaxBrowsers,
PageMaxTimeout: opts.PageMaxTimeout,
ShowBrowser: opts.ShowBrowser,
RequestCallback: opts.RequestCallback,
SlowMotion: opts.SlowMotion,
ScopeValidator: opts.ScopeValidator,
ChromeUser: opts.ChromeUser,
Trace: opts.Trace,
CookieConsentBypass: opts.CookieConsentBypass,
NoSandbox: opts.NoSandbox,
Proxy: opts.Proxy,
})
if err != nil {
return nil, err
}
var diagnosticsWriter diagnostics.Writer
if opts.EnableDiagnostics {
directory := opts.DiagnosticsDir
if directory == "" {
cwd, _ := os.Getwd()
directory = filepath.Join(cwd, fmt.Sprintf("katana-diagnostics-%s", time.Now().Format(time.RFC3339)))
}
writer, err := diagnostics.NewWriter(directory)
if err != nil {
return nil, err
}
diagnosticsWriter = writer
opts.DiagnosticsDir = directory
opts.Logger.Info("Diagnostics enabled", slog.String("directory", directory))
}
crawler := &Crawler{
launcher: launcher,
options: opts,
logger: opts.Logger,
uniqueActions: make(map[string]struct{}),
diagnostics: diagnosticsWriter,
simhashOracle: simhash.NewOracle(),
}
return crawler, nil
}
func (c *Crawler) Close() {
c.launcher.Close()
if c.diagnostics != nil {
if err := c.diagnostics.Close(); err != nil {
c.logger.Warn("Failed to close diagnostics", slog.String("error", err.Error()))
}
}
}
func (c *Crawler) GetCrawlGraph() *graph.CrawlGraph {
return c.crawlGraph
}
func (c *Crawler) Crawl(URL string) error {
defer func() {
if c.diagnostics == nil {
return
}
err := c.crawlGraph.DrawGraph(filepath.Join(c.options.DiagnosticsDir, "crawl-graph.dot"))
if err != nil {
c.logger.Error("Failed to draw crawl graph", slog.String("error", err.Error()))
}
}()
actions := []*types.Action{{
Type: types.ActionTypeLoadURL,
Input: URL,
Depth: 0,
OriginID: emptyPageHash,
}}
crawlQueue := queue.NewLinked(actions)
c.crawlQueue = crawlQueue
crawlGraph := graph.NewCrawlGraph()
c.crawlGraph = crawlGraph
// Add the initial blank state
err := crawlGraph.AddPageState(types.PageState{
UniqueID: emptyPageHash,
URL: "about:blank",
Depth: 0,
})
if err != nil {
return err
}
// Create a master context that will automatically cancel all page operations
// once the per-URL crawl deadline is reached.
var (
ctx context.Context
cancel context.CancelFunc
)
if c.options.MaxCrawlDuration > 0 {
ctx, cancel = context.WithTimeout(context.Background(), c.options.MaxCrawlDuration)
} else {
ctx, cancel = context.WithCancel(context.Background())
}
defer cancel()
// Retain the legacy time.After guard as a secondary fail-safe but the
// context cancellation is what actually stops in-flight rod calls.
var crawlTimeout <-chan time.Time
if c.options.MaxCrawlDuration > 0 {
crawlTimeout = time.After(c.options.MaxCrawlDuration)
}
consecutiveFailures := 0
for {
select {
case <-crawlTimeout:
c.logger.Debug("Max crawl duration reached, stopping crawl")
return nil
default:
// Check for too many failures
if c.options.MaxFailureCount > 0 && consecutiveFailures >= c.options.MaxFailureCount {
c.logger.Warn("Too many consecutive failures, stopping crawl",
slog.Int("failures", consecutiveFailures),
slog.Int("max_allowed", c.options.MaxFailureCount),
slog.Int("remaining_actions", c.crawlQueue.Size()),
)
return nil
}
action, err := crawlQueue.Get()
if err == queue.ErrNoElementsAvailable {
c.logger.Debug("No more actions to process")
return nil
}
if err != nil {
return err
}
if c.options.MaxDepth > 0 && action.Depth > c.options.MaxDepth {
continue
}
page, err := c.launcher.GetPageFromPool()
if err != nil {
return err
}
page.Page = page.Context(ctx)
c.logger.Debug("Processing action",
slog.String("action", action.String()),
)
if err := c.crawlFn(ctx, action, page); err != nil {
if err == ErrNoCrawlingAction {
return nil
}
if errors.Is(err, ErrElementNotVisible) {
consecutiveFailures++
continue
}
var npe *rod.NoPointerEventsError
var ish *rod.InvisibleShapeError
if errors.As(err, &npe) || errors.As(err, &ish) {
c.logger.Debug("Skipping action as it is not visible",
slog.String("action", action.String()),
slog.String("error", err.Error()),
)
consecutiveFailures++
continue
}
var ne *rod.NavigationError
if errors.As(err, &ne) {
c.logger.Debug("Skipping action as navigation failed",
slog.String("action", action.String()),
slog.String("error", err.Error()),
)
consecutiveFailures++
continue
}
if errors.Is(err, ErrNoNavigationPossible) {
c.logger.Debug("Skipping action as no navigation possible", slog.String("action", action.String()))
consecutiveFailures++
continue
}
var msce *utils.MaxSleepCountError
if errors.As(err, &msce) {
c.logger.Debug("Skipping action as it is taking too long", slog.String("action", action.String()))
consecutiveFailures++
continue
}
c.logger.Debug("Skipping action due to site-specific error",
slog.String("error", err.Error()),
slog.String("action", action.String()),
)
consecutiveFailures++
continue
}
consecutiveFailures = 0
}
}
}
var ErrNoCrawlingAction = errors.New("no more actions to crawl")
func (c *Crawler) crawlFn(ctx context.Context, action *types.Action, page *browser.BrowserPage) error {
defer func() {
c.launcher.PutBrowserToPool(page)
}()
currentPageHash, _, err := getPageHash(page)
if err != nil {
return err
}
c.logger.Debug("Processing action - current state",
slog.String("current_page_hash", currentPageHash),
slog.String("action_origin_id", action.OriginID),
slog.String("action", action.String()),
)
if action.OriginID != "" && action.OriginID != currentPageHash {
c.logger.Debug("Need to navigate back to origin",
slog.String("from", currentPageHash),
slog.String("to", action.OriginID),
)
newPageHash, err := c.navigateBackToStateOrigin(action, page, currentPageHash)
if err != nil {
return err
}
// Refresh the page hash
currentPageHash = newPageHash
}
// FIXME: TODO: Restrict the navigation using scope manager and only
// proceed with actions if the scope is allowed
// Check the action and do actions based on action type
if c.diagnostics != nil {
if err := c.diagnostics.LogAction(action); err != nil {
return err
}
}
if err := c.executeCrawlStateAction(action, page); err != nil {
return err
}
// Check for captcha pages after navigation and attempt to solve them.
// On success, wait for the page to settle and re-enter crawlFn so navigation
// discovery runs on the post-solve page instead of the captcha page.
if c.options.CaptchaHandler != nil {
html, htmlErr := page.HTML()
if htmlErr == nil {
handled, solveErr := c.options.CaptchaHandler.HandleIfCaptcha(ctx, page.Page, html)
if solveErr != nil {
gologger.Warning().Msgf("captcha solving failed: %s", solveErr)
}
if handled && solveErr == nil {
_ = page.WaitPageLoadHeurisitics()
}
if handled {
// Skip navigation discovery on captcha pages — the discovered
// links/forms belong to the captcha widget, not the real page.
return nil
}
}
}
pageState, err := newPageState(page, action)
if err != nil {
return err
}
if c.diagnostics != nil {
if err := c.diagnostics.LogPageState(pageState, diagnostics.PostActionPageState); err != nil {
return err
}
}
pageState.OriginID = currentPageHash
if c.options.ScopeValidator != nil {
if !c.options.ScopeValidator(pageState.URL) {
c.logger.Debug("Skipping navigation collection - current page is out of scope",
slog.String("url", pageState.URL),
)
if c.crawlQueue.Size() == 0 {
return ErrNoCrawlingAction
}
return nil
}
}
navigations, err := page.FindNavigations()
if err != nil {
return err
}
// Log navigations for diagnostics
if c.diagnostics != nil {
screenshotState, err := page.Screenshot(false, &proto.PageCaptureScreenshot{
Format: proto.PageCaptureScreenshotFormatPng,
})
if err != nil {
c.logger.Error("Failed to take screenshot", slog.String("error", err.Error()))
}
if err := c.diagnostics.LogPageStateScreenshot(pageState.UniqueID, screenshotState); err != nil {
c.logger.Error("Failed to log page state screenshot", slog.String("error", err.Error()))
}
if err := c.diagnostics.LogNavigations(pageState.UniqueID, navigations); err != nil {
c.logger.Error("Failed to log navigations", slog.String("error", err.Error()))
}
}
for _, nav := range navigations {
actionHash := nav.Hash()
if _, ok := c.uniqueActions[actionHash]; ok {
continue
}
c.uniqueActions[actionHash] = struct{}{}
// Check if the element we have is a logout page
if nav.Element != nil && isLogoutPage(nav.Element) {
c.logger.Debug("Skipping Found logout page",
slog.String("url", nav.Element.Attributes["href"]),
)
continue
}
nav.OriginID = pageState.UniqueID
c.logger.Debug("Got new navigation",
slog.Any("navigation", nav),
)
if err := c.crawlQueue.Offer(nav); err != nil {
return err
}
}
err = c.crawlGraph.AddPageState(*pageState)
if err != nil {
return err
}
// TODO: Check if the page opened new sub pages and if so capture their
// navigation as well as close them so the state change can work.
if len(navigations) == 0 && c.crawlQueue.Size() == 0 {
return ErrNoCrawlingAction
}
return nil
}
var ErrElementNotVisible = errors.New("element not visible")
func (c *Crawler) executeCrawlStateAction(action *types.Action, page *browser.BrowserPage) error {
var err error
switch action.Type {
case types.ActionTypeLoadURL:
// Apply a timeout to every critical Rod call.
pTimeout := page.Timeout(c.options.PageMaxTimeout)
if err := pTimeout.Navigate(action.Input); err != nil {
return err
}
if err = page.WaitPageLoadHeurisitics(); err != nil {
return err
}
case types.ActionTypeFillForm:
if err := c.processForm(page, action.Form); err != nil {
return err
}
case types.ActionTypeLeftClick, types.ActionTypeLeftClickDown:
pTimeout := page.Timeout(c.options.PageMaxTimeout)
element, err := pTimeout.ElementX(action.Element.XPath)
if err != nil {
return err
}
elementTimeout := element.Timeout(c.options.PageMaxTimeout)
if err := elementTimeout.ScrollIntoView(); err != nil {
return err
}
visible, err := element.Visible()
if err != nil {
return err
}
if !visible {
return ErrElementNotVisible
}
// Check if element is interactable (not blocked by overlays)
interactable, err := element.Interactable()
if err != nil {
var ce *rod.CoveredError
if errors.As(err, &ce) {
return ErrElementNotVisible
}
return err
}
if interactable == nil {
return ErrElementNotVisible
}
if err := element.Click(proto.InputMouseButtonLeft, 1); err != nil {
return err
}
if err = page.WaitPageLoadHeurisitics(); err != nil {
return err
}
default:
return fmt.Errorf("unknown action type: %v", action.Type)
}
return nil
}
var logoutPattern = regexp.MustCompile(`(?i)(log[\s-]?out|sign[\s-]?out|signout|deconnexion|cerrar[\s-]?sesion|sair|abmelden|uitloggen|ausloggen|exit|disconnect|terminate|end[\s-]?session|salir|desconectar|afmelden|wyloguj|logout|sign[\s-]?off)`)
func isLogoutPage(element *types.HTMLElement) bool {
return logoutPattern.MatchString(element.TextContent) ||
logoutPattern.MatchString(element.Attributes["href"])
}
================================================
FILE: pkg/engine/headless/crawler/diagnostics/diagnostics.go
================================================
package diagnostics
import (
"encoding/json"
"os"
"path/filepath"
"sync"
"time"
"github.com/projectdiscovery/katana/pkg/engine/headless/types"
mapsutil "github.com/projectdiscovery/utils/maps"
)
// Writer is a writer that writes diagnostics to a directory
// for the katana headless crawler module.
type Writer interface {
Close() error
LogAction(action *types.Action) error
LogPageState(state *types.PageState, stateType PageStateType) error
LogNavigations(pageStateID string, navigations []*types.Action) error
LogPageStateScreenshot(pageStateID string, screenshot []byte) error
}
type PageStateType string
var (
PreActionPageState PageStateType = "pre-action"
PostActionPageState PageStateType = "post-action"
)
type diskWriter struct {
index mapsutil.OrderedMap[string, *stateMetadata]
actions []*types.Action
mu sync.Mutex
directory string
}
type stateMetadata struct {
UniqueID string `json:"unique_id"`
URL string `json:"url"`
Title string `json:"title"`
Occurence int `json:"occurence"`
Type string `json:"type"`
}
type navigationEntry struct {
PageStateID string `json:"page_state_id"`
URL string `json:"url"`
NavigationCount int `json:"navigation_count"`
Navigations []*types.Action `json:"navigations"`
Timestamp int64 `json:"timestamp"`
}
// NewWriter creates a new Writer.
func NewWriter(directory string) (Writer, error) {
if err := os.MkdirAll(directory, 0755); err != nil {
return nil, err
}
return &diskWriter{
directory: directory,
index: mapsutil.NewOrderedMap[string, *stateMetadata](),
actions: make([]*types.Action, 0),
mu: sync.Mutex{},
}, nil
}
func (w *diskWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
actionsList := w.actions
marshallIndented, err := json.MarshalIndent(actionsList, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(filepath.Join(w.directory, "actions.json"), marshallIndented, 0644); err != nil {
return err
}
// Write index to a separate file
var data []*stateMetadata
w.index.Iterate(func(key string, value *stateMetadata) bool {
data = append(data, value)
return true
})
marshallIndented, err = json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(w.directory, "index.json"), marshallIndented, 0644)
}
func (w *diskWriter) LogAction(action *types.Action) error {
w.mu.Lock()
defer w.mu.Unlock()
w.actions = append(w.actions, action)
return nil
}
func (w *diskWriter) LogPageState(state *types.PageState, stateType PageStateType) error {
w.mu.Lock()
val, ok := w.index.Get(state.UniqueID)
if ok && val != nil {
val.Occurence++
w.mu.Unlock()
return nil
}
w.index.Set(state.UniqueID, &stateMetadata{
URL: state.URL,
Title: state.Title,
Occurence: 1,
Type: string(stateType),
UniqueID: state.UniqueID,
})
w.mu.Unlock()
// Write dom to a separate file and remove striped dom
// Create new directory for each state
dom, strippedDOM := state.DOM, state.StrippedDOM
dir := filepath.Join(w.directory, state.UniqueID)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
domFile := filepath.Join(dir, "dom.html")
if err := os.WriteFile(domFile, []byte(dom), 0644); err != nil {
return err
}
strippedDOMFile := filepath.Join(dir, "stripped-dom.html")
if err := os.WriteFile(strippedDOMFile, []byte(strippedDOM), 0644); err != nil {
return err
}
return nil
}
func (w *diskWriter) LogNavigations(pageStateID string, navigations []*types.Action) error {
w.mu.Lock()
defer w.mu.Unlock()
metadata, exists := w.index.Get(pageStateID)
url := ""
if exists && metadata != nil {
url = metadata.URL
}
dir := filepath.Join(w.directory, pageStateID)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
navigationsFile := filepath.Join(dir, "navigations.json")
var entry navigationEntry
if existingData, err := os.ReadFile(navigationsFile); err == nil {
if err := json.Unmarshal(existingData, &entry); err != nil {
return err
}
entry.Navigations = append(entry.Navigations, navigations...)
entry.NavigationCount = len(entry.Navigations)
entry.Timestamp = time.Now().Unix()
} else {
entry = navigationEntry{
PageStateID: pageStateID,
URL: url,
NavigationCount: len(navigations),
Navigations: navigations,
Timestamp: time.Now().Unix(),
}
}
marshalledData, err := json.MarshalIndent(entry, "", " ")
if err != nil {
return err
}
// Write to navigations.json file in the state directory
return os.WriteFile(navigationsFile, marshalledData, 0644)
}
func (w *diskWriter) LogPageStateScreenshot(pageStateID string, screenshot []byte) error {
w.mu.Lock()
defer w.mu.Unlock()
dir := filepath.Join(w.directory, pageStateID)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
screenshotFile := filepath.Join(dir, "screenshot.png")
return os.WriteFile(screenshotFile, screenshot, 0644)
}
================================================
FILE: pkg/engine/headless/crawler/formfill.go
================================================
package crawler
import (
"fmt"
"log/slog"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
"github.com/projectdiscovery/katana/pkg/engine/headless/browser"
"github.com/projectdiscovery/katana/pkg/engine/headless/types"
utilsformfill "github.com/projectdiscovery/katana/pkg/utils"
mapsutil "github.com/projectdiscovery/utils/maps"
)
func deriveName(e *types.HTMLElement) string {
if n, ok := e.Attributes["name"]; ok && n != "" {
return n
}
return e.ID
}
func copyAttrs(src map[string]string, skipKeys ...string) mapsutil.OrderedMap[string, string] {
skip := map[string]struct{}{}
for _, k := range skipKeys {
skip[k] = struct{}{}
}
dst := mapsutil.NewOrderedMap[string, string]()
for k, v := range src {
if _, s := skip[k]; !s {
dst.Set(k, v)
}
}
return dst
}
func convertHTMLElementToFormInput(element *types.HTMLElement) utilsformfill.FormInput {
return utilsformfill.FormInput{
Name: deriveName(element),
Type: element.Type,
Value: element.Value,
Attributes: copyAttrs(element.Attributes, "name", "value", "type"),
}
}
func convertHTMLElementToFormTextArea(element *types.HTMLElement) utilsformfill.FormTextArea {
return utilsformfill.FormTextArea{
Name: deriveName(element),
Attributes: copyAttrs(element.Attributes, "name"),
}
}
func convertHTMLElementToFormSelect(element *types.HTMLElement) utilsformfill.FormSelect {
return utilsformfill.FormSelect{
Name: deriveName(element),
Attributes: copyAttrs(element.Attributes, "name"),
SelectOptions: []utilsformfill.SelectOption{},
}
}
func (c *Crawler) processForm(page *browser.BrowserPage, form *types.HTMLForm) error {
if !c.options.AutomaticFormFill {
return nil
}
var formFields []interface{}
var submitButton *rod.Element
elementMap := make(map[string]*rod.Element)
for _, field := range form.Elements {
if field.XPath == "" {
continue
}
element, err := page.ElementX(field.XPath)
if err != nil {
c.logger.Debug("Could not find form element",
slog.String("xpath", field.XPath),
slog.String("error", err.Error()),
)
continue
}
fieldName := c.getFieldName(field)
switch field.TagName {
case "INPUT":
if field.Type == "submit" || field.Type == "button" {
if submitButton == nil && field.Type == "submit" {
submitButton = element
}
continue
}
formInput := convertHTMLElementToFormInput(field)
formFields = append(formFields, formInput)
if fieldName != "" {
elementMap[fieldName] = element
}
case "TEXTAREA":
formTextArea := convertHTMLElementToFormTextArea(field)
formFields = append(formFields, formTextArea)
if fieldName != "" {
elementMap[fieldName] = element
}
case "SELECT":
formSelect := c.buildFormSelectWithOptions(page, field, element)
formFields = append(formFields, formSelect)
if fieldName != "" {
elementMap[fieldName] = element
}
case "BUTTON":
if field.Type == "submit" && submitButton == nil {
submitButton = element
}
}
}
fillSuggestions := utilsformfill.FormFillSuggestions(formFields)
if err := c.applyFormSuggestions(fillSuggestions, elementMap); err != nil {
c.logger.Debug("Error applying form suggestions", slog.String("error", err.Error()))
}
if submitButton != nil {
if err := submitButton.Click(proto.InputMouseButtonLeft, 1); err != nil {
return err
}
}
return nil
}
func (c *Crawler) getFieldName(field *types.HTMLElement) string {
return deriveName(field)
}
func (c *Crawler) buildFormSelectWithOptions(page *browser.BrowserPage, field *types.HTMLElement, element *rod.Element) utilsformfill.FormSelect {
formSelect := convertHTMLElementToFormSelect(field)
options, err := element.Elements("option")
if err == nil && len(options) > 0 {
formSelect.SelectOptions = []utilsformfill.SelectOption{}
for _, opt := range options {
optionValue, _ := opt.Attribute("value")
if optionValue == nil {
text, _ := opt.Text()
optionValue = &text
}
selected, _ := opt.Attribute("selected")
selectOption := utilsformfill.SelectOption{
Value: *optionValue,
Selected: "",
}
if selected != nil {
selectOption.Selected = "selected"
}
formSelect.SelectOptions = append(formSelect.SelectOptions, selectOption)
}
} else {
formSelect.SelectOptions = []utilsformfill.SelectOption{
{Value: utilsformfill.FormData.Placeholder, Selected: "selected"},
}
}
return formSelect
}
func (c *Crawler) applyFormSuggestions(suggestions mapsutil.OrderedMap[string, string], elementMap map[string]*rod.Element) error {
suggestions.Iterate(func(fieldName, value string) bool {
element, exists := elementMap[fieldName]
if !exists || value == "" {
return true
}
tagName, err := element.Eval(`() => this.tagName`)
if err != nil {
c.logger.Debug("Failed to get element tag",
slog.String("field", fieldName),
slog.String("error", err.Error()),
)
return true
}
switch tagName.Value.String() {
case "INPUT":
inputType, _ := element.Attribute("type")
if inputType != nil {
switch *inputType {
case "checkbox", "radio":
if value == "on" || value == fieldName {
if err := element.Click(proto.InputMouseButtonLeft, 1); err != nil {
c.logger.Debug("Failed to check input",
slog.String("field", fieldName),
slog.String("type", *inputType),
slog.String("error", err.Error()),
)
}
}
default:
if err := element.Input(value); err != nil {
c.logger.Debug("Failed to fill input field",
slog.String("field", fieldName),
slog.String("value", value),
slog.String("error", err.Error()),
)
}
}
}
case "TEXTAREA":
if err := element.Input(value); err != nil {
c.logger.Debug("Failed to fill textarea",
slog.String("field", fieldName),
slog.String("value", value),
slog.String("error", err.Error()),
)
}
case "SELECT":
if err := element.Select([]string{value}, true, rod.SelectorTypeText); err != nil {
valueSelector := fmt.Sprintf(`[value="%s"]`, value)
if err := element.Select([]string{valueSelector}, true, rod.SelectorTypeCSSSector); err != nil {
c.logger.Debug("Failed to select option",
slog.String("field", fieldName),
slog.String("value", value),
slog.String("error", err.Error()),
)
}
}
}
return true
})
return nil
}
================================================
FILE: pkg/engine/headless/crawler/normalizer/dom_utils.go
================================================
package normalizer
import (
"strings"
"github.com/PuerkitoBio/goquery"
"golang.org/x/net/html"
)
// DefaultDOMTransformations is default list of CSS selectors to remove from the DOM.
var DefaultDOMTransformations = []string{
"style, script, path", // remove script and style tags
"input[type='hidden']", // remove hidden inputs
"meta[content]", // remove meta tags with content
"link[rel='stylesheet']", // remove stylesheet links
"svg", // remove svg
"grammarly-desktop-integration", // remove grammarly
"div[class*='ad'], div[id*='ad'], div[class*='banner'], div[id*='banner'], div[class*='pixel'], div[id*='pixel']", // remove ad, banner and pixel divs
"input[name*='csrf'], input[name*='token']", // remove csrf and token inputs
}
// NoChildrenDomTransformations removes all elements with no children
var NoChildrenDomTransformations = []string{
"div", // remove divs with no children
"span", // remove spans with no children
"form", // remove forms with no children
"iframe", // remove iframes
}
// DOMNormalizer is a normalizer for DOM content
type DOMNormalizer struct {
customTransformations []domTransformationFunc
}
// NewDOMNormalizer returns a new DOMNormalizer
//
// transformations is a list of CSS selectors to remove from the DOM.
func NewDOMNormalizer() *DOMNormalizer {
var customTransformations []domTransformationFunc
for _, t := range DefaultDOMTransformations {
t := t
customTransformations = append(customTransformations, func(doc *goquery.Document) {
doc.Find(t).Each(func(_ int, s *goquery.Selection) {
s.Remove()
})
})
}
for _, t := range NoChildrenDomTransformations {
t := t
customTransformations = append(customTransformations, func(doc *goquery.Document) {
doc.Find(t).Each(func(_ int, s *goquery.Selection) {
if s.Children().Length() == 0 && strings.TrimSpace(s.Text()) == "" {
if node := s.Get(0); node != nil && len(node.Attr) == 0 {
s.Remove()
}
}
})
})
}
return &DOMNormalizer{customTransformations: customTransformations}
}
// Apply applies the normalizers to the given content
func (d *DOMNormalizer) Apply(content string) (string, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
if err != nil {
return "", err
}
// Apply custom transformations first (selector-based removals, etc.)
for _, f := range d.customTransformations {
f(doc)
}
// Apply selection based transformations once at the root (recursive helpers will traverse)
for _, f := range selectionBasedTransformationFuncs {
f(doc.Selection)
}
result, err := doc.Html()
if err != nil {
return "", err
}
return result, nil
}
// domTransformationFunc does required transformation on document.
type domTransformationFunc func(doc *goquery.Document)
type selectionTransformationFunc func(s *goquery.Selection)
var selectionBasedTransformationFuncs = []selectionTransformationFunc{
removeCommentsDomTransformationFunc, // remove comments
removeClassIDDataAttributesDomTransformationFunc, // remove class, id and data attributes
}
func removeComments(n *html.Node) {
if n.Type == html.CommentNode {
n.Parent.RemoveChild(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
removeComments(c)
}
}
func removeCommentsDomTransformationFunc(s *goquery.Selection) {
if n := s.Get(0); n != nil {
removeComments(n)
}
}
var attributes = []string{
"class",
"id",
"name",
"href",
"style",
"width",
"height",
"src",
"nowrap",
"target",
"valign",
"cellpadding",
"cellspacing",
"value",
"placeholder",
"title",
"alt",
}
func removeClassIDDataAttributesDomTransformationFunc(s *goquery.Selection) {
removeAttributes(s)
// Handle children
s.Children().Each(func(_ int, child *goquery.Selection) {
removeClassIDDataAttributesDomTransformationFunc(child)
})
}
func removeAttributes(s *goquery.Selection) {
for _, attr := range attributes {
s.RemoveAttr(attr)
}
for _, node := range s.Nodes {
for _, attr := range node.Attr {
attr := attr
if strings.HasPrefix(attr.Key, "data-") || strings.HasPrefix(attr.Key, "aria-") || strings.HasPrefix(attr.Key, "js") {
s.RemoveAttr(attr.Key)
}
}
}
}
================================================
FILE: pkg/engine/headless/crawler/normalizer/dom_utils_test.go
================================================
package normalizer
import (
"testing"
)
func TestDOMNormalizer_Apply(t *testing.T) {
type args struct {
content string
}
normalizer := NewDOMNormalizer()
tests := []struct {
name string
d *DOMNormalizer
args args
want string
wantErr bool
}{
{
name: "comments-style-script",
d: normalizer,
args: args{
content: `Hello World `,
},
want: "Hello World ",
wantErr: false,
},
{
name: "hidden input",
d: normalizer,
args: args{
content: ` `,
},
want: "",
wantErr: false,
},
// write tests for other cases
{
name: "csrf",
d: normalizer,
args: args{
content: ` `,
},
want: "",
wantErr: false,
},
{
name: "class-id-data-attributes",
d: normalizer,
args: args{
content: `
`,
},
want: "
",
},
{
name: "inline-style",
d: normalizer,
args: args{
content: `
`,
},
want: "
",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := normalizer
got, err := d.Apply(tt.args.content)
if (err != nil) != tt.wantErr {
t.Errorf("DOMNormalizer.Apply() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("DOMNormalizer.Apply() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: pkg/engine/headless/crawler/normalizer/helpers.go
================================================
package normalizer
// dateTimePatterns contains regex patterns for various date and time formats
// The ordering is important for proper matching
var dateTimePatterns = []string{
/* with days */
"[a-zA-Z]{3,} [0-9]{1,2} [a-zA-Z]{3,} [0-9]{4}",
"[a-zA-Z]{3,} [0-9]{1,2} [a-zA-Z]{3,} '[0-9]{2}",
"[a-zA-Z]{3,} [0-9]{1,2} [a-zA-Z]{3,}",
/* only numeric */
"[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}",
"[0-9]{4}\\.[0-9]{1,2}\\.[0-9]{1,2}",
"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}",
"[0-9]{1,2}-[0-9]{1,2}-[0-9]{4}",
"[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{4}",
"[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}",
"[0-9]{1,2}-[0-9]{1,2}-'[0-9]{2}",
"[0-9]{1,2}\\.[0-9]{1,2}\\.'[0-9]{2}",
"[0-9]{1,2}/[0-9]{1,2}/'[0-9]{2}",
"[0-9]{1,2}-[0-9]{1,2}-[0-9]{2}",
"[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{2}",
"[0-9]{1,2}/[0-9]{1,2}/[0-9]{2}",
/* long months */
"[0-9]{1,2} [a-zA-Z]{3,} [0-9]{4}",
"[0-9]{1,2}th [a-zA-Z]{3,} [0-9]{4}",
"[0-9]{1,2}th [a-zA-Z]{3,}",
"[0-9]{4} [a-zA-Z]{3,} [0-9]{1,2}",
"[0-9]{4}[a-zA-Z]{3,}[0-9]{1,2}",
"[a-zA-Z]{3,} [0-9]{4}",
"[a-zA-Z]{3,} '[0-9]{2}",
"[a-zA-Z]{3,} [0-9]{1,2} [0-9]{4}",
"[a-zA-Z]{3,} [0-9]{1,2}, [0-9]{4}",
"[a-zA-Z]{3,} [0-9]{1,2} '[0-9]{2}",
"[a-zA-Z]{3,} [0-9]{1,2}, '[0-9]{2}",
/* Times */
"[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}( )?(pm|PM|am|AM)",
"[0-9]{1,2}:[0-9]{1,2}( )?(pm|PM|am|AM)",
"[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}",
"[0-9]{1,2}:[0-9]{1,2}",
}
================================================
FILE: pkg/engine/headless/crawler/normalizer/normalizer.go
================================================
package normalizer
import (
"fmt"
"html"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/pkg/errors"
htmlpkg "golang.org/x/net/html"
)
var whiteSpacesRegex = regexp.MustCompile(`[\r\n]+|\s+`)
type Normalizer struct {
dom *DOMNormalizer
text *TextNormalizer
}
// New returns a new Normalizer
func New() (*Normalizer, error) {
textNormalizer, err := NewTextNormalizer()
if err != nil {
return nil, errors.Wrap(err, "failed to create text normalizer")
}
domNormalizer := NewDOMNormalizer()
return &Normalizer{
dom: domNormalizer,
text: textNormalizer,
}, nil
}
// Apply applies the normalizers to the given content
//
// It normalizes the given content by:
// - Applying the DOM normalizer
// - Applying the text normalizer
// - Denormalizing it
func (n *Normalizer) Apply(text string) (string, error) {
first := normalizeDocument(text)
firstpass, err := n.dom.Apply(first)
if err != nil {
return "", errors.Wrap(err, "failed to apply DOM normalizer")
}
secondpass, err := stripTextContent(firstpass)
if err != nil {
return "", errors.Wrap(err, "failed to strip text content")
}
thirdpass := n.text.Apply(secondpass)
fourthpass := normalizeDocument(thirdpass)
return fourthpass, nil
}
// normalizeDocument normalizes the given document by:
// - Lowercasing it
// - URL decoding it
// - HTML entity decoding it
// - Replacing all whitespace variations with a space
// - Trimming the document whitespaces
func normalizeDocument(text string) string {
// Lowercase the document
lowercased := strings.ToLower(text)
// Convert hexadecimal escape sequences to HTML entities
converted := convertHexEscapeSequencesToEntities(lowercased)
unescaped := html.UnescapeString(converted)
// URL Decode and HTML entity decode the document to standardize it.
urlDecoded, err := url.QueryUnescape(unescaped)
if err != nil {
urlDecoded = unescaped
}
// Replace all whitespaces with a space
normalized := whiteSpacesRegex.ReplaceAllString(urlDecoded, " ")
// Trim the document to remove leading and trailing whitespaces
return strings.Trim(normalized, " \r\n\t")
}
func replaceHexEscapeSequence(match string) string {
// Remove the '\x' prefix
code := strings.TrimPrefix(match, "\\x")
// Parse the hexadecimal code to an integer
value, err := strconv.ParseInt(code, 16, 32)
if err != nil {
// If there's an error, return the original match
return match
}
// Return the corresponding HTML entity
return fmt.Sprintf("%x;", value)
}
// Define the regex pattern to match hexadecimal escape sequences
var pattern = regexp.MustCompile(`\\x[0-9a-fA-F]{2}`)
func convertHexEscapeSequencesToEntities(input string) string {
return pattern.ReplaceAllStringFunc(input, func(match string) string {
return replaceHexEscapeSequence(match)
})
}
func stripTextContent(content string) (string, error) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(content))
if err != nil {
return "", err
}
doc.Find("h1, h2, h3, h4, h5, h6, p, span, div, td, th, li, a").Each(func(_ int, s *goquery.Selection) {
removeTextNodesFromSelection(s)
})
result, err := doc.Html()
if err != nil {
return "", err
}
return result, nil
}
func removeTextNodesFromSelection(s *goquery.Selection) {
node := s.Get(0)
if node == nil {
return
}
for c := node.FirstChild; c != nil; {
next := c.NextSibling
if c.Type == htmlpkg.TextNode {
node.RemoveChild(c)
}
c = next
}
}
================================================
FILE: pkg/engine/headless/crawler/normalizer/simhash/simhash.go
================================================
// Package simhash implements SimHash algorithm for near-duplicate detection.
//
// The original algorithm is taken from: https://github.com/yahoo/gryffin/blob/master/html-distance/feature.go
// Optimized implementation with performance improvements.
//
// Original Copyright 2015, Yahoo Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simhash
import (
"bytes"
"fmt"
"io"
"sync"
"github.com/mfonda/simhash"
"golang.org/x/net/html"
)
// Constants for optimization
const (
maxTokens = 5000
featuresBufSize = 1000
)
// Pre-allocated buffers for feature generation
var bufferPool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
func fingerprintOptimized(r io.Reader, shingle int) uint64 {
if shingle < 1 {
shingle = 1
}
v := simhash.Vector{}
z := html.NewTokenizer(r)
features := make([]string, 0, featuresBufSize)
window := make([][]byte, shingle)
windowIndex := 0
// Single-pass tokenization and feature extraction
count := 0
for count < maxTokens {
if tt := z.Next(); tt == html.ErrorToken {
break
}
t := z.Token()
count++
extractFeatures(&t, &features)
}
// Process features with shingling
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
for _, f := range features {
window[windowIndex%shingle] = []byte(f)
windowIndex++
buf.Reset()
for i := 0; i < shingle; i++ {
if i > 0 {
buf.WriteByte(' ')
}
buf.Write(window[(windowIndex-shingle+i+shingle)%shingle])
}
sum := simhash.NewFeature(buf.Bytes()).Sum()
for i := uint8(0); i < 64; i++ {
if (sum>>i)&1 == 1 {
v[i]++
} else {
v[i]--
}
}
}
return simhash.Fingerprint(v)
}
// extractFeatures extracts features from HTML token and appends to slice
func extractFeatures(t *html.Token, features *[]string) {
// Pre-allocate string builder for efficiency
var s string
switch t.Type {
case html.StartTagToken:
s = "A:" + t.DataAtom.String()
case html.EndTagToken:
s = "B:" + t.DataAtom.String()
case html.SelfClosingTagToken:
s = "C:" + t.DataAtom.String()
case html.DoctypeToken:
s = "D:" + string(t.Data)
case html.CommentToken:
s = "E:" + string(t.Data)
case html.TextToken:
s = "F:" + string(t.Data)
case html.ErrorToken:
s = "Z:" + string(t.Data)
default:
return
}
*features = append(*features, s)
// Process attributes
for _, attr := range t.Attr {
switch attr.Key {
case "class", "name", "rel":
s = fmt.Sprintf("G:%s:%s:%s", t.DataAtom.String(), attr.Key, attr.Val)
default:
s = fmt.Sprintf("G:%s:%s", t.DataAtom.String(), attr.Key)
}
*features = append(*features, s)
}
}
// Fingerprint is the original function signature for compatibility
func Fingerprint(r io.Reader, shingle int) uint64 {
return fingerprintOptimized(r, shingle)
}
type Oracle struct {
fingerprint uint64 // node value.
nodes [65]*Oracle // leaf nodes
}
// NewOracle return an oracle that could tell if the fingerprint has been seen or not.
func NewOracle() *Oracle {
return newNode(0)
}
func newNode(f uint64) *Oracle {
return &Oracle{fingerprint: f}
}
// Distance return the similarity distance between two fingerprint.
func Distance(a, b uint64) uint8 {
return simhash.Compare(a, b)
}
// See asks the oracle to see the fingerprint.
func (n *Oracle) See(f uint64) *Oracle {
d := Distance(n.fingerprint, f)
if d == 0 {
// current node with same fingerprint.
return n
}
// the target node is already set,
if c := n.nodes[d]; c != nil {
return c.See(f)
}
n.nodes[d] = newNode(f)
return n.nodes[d]
}
// Seen asks the oracle if anything closed to the fingerprint in a range (r) is seen before.
func (n *Oracle) Seen(f uint64, r uint8) bool {
d := Distance(n.fingerprint, f)
if d <= r {
return true
}
// Check the direct child at distance d first
if c := n.nodes[d]; c != nil && c.Seen(f, r) {
return true
}
// Optimized search: start from closest distance and expand outward
for offset := uint8(1); offset <= r; offset++ {
// Check both directions
if d >= offset {
if c := n.nodes[d-offset]; c != nil && c.Seen(f, r) {
return true
}
}
if d+offset <= 64 {
if c := n.nodes[d+offset]; c != nil && c.Seen(f, r) {
return true
}
}
}
return false
}
================================================
FILE: pkg/engine/headless/crawler/normalizer/simhash/simhash_test.go
================================================
package simhash
import (
"strings"
"testing"
)
var (
htmlA = `Hello World
`
// htmlB differs by an exclamation mark. This should keep the documents fairly similar
// while still resulting in a different SimHash fingerprint.
htmlB = `Hello World!
`
)
// TestFingerprintDeterministic ensures that hashing the same document twice produces
// identical fingerprints and that a shingle value of 0 gracefully falls back to 1.
func TestFingerprintDeterministic(t *testing.T) {
in1 := strings.NewReader(htmlA)
in2 := strings.NewReader(htmlA)
fp1 := Fingerprint(in1, 0)
fp2 := Fingerprint(in2, 1)
if fp1 != fp2 {
t.Fatalf("expected identical fingerprints, got %d and %d", fp1, fp2)
}
}
// TestFingerprintSimilarity checks that two similar documents yield fingerprints with
// a small (non-zero) Hamming distance.
func TestFingerprintSimilarity(t *testing.T) {
fpA := Fingerprint(strings.NewReader(htmlA), 3)
fpB := Fingerprint(strings.NewReader(htmlB), 3)
d := Distance(fpA, fpB)
if d == 0 {
t.Fatalf("expected different fingerprints, got distance 0")
}
const maxReasonableDistance = 20 // out of a maximum of 64
if d > maxReasonableDistance {
t.Fatalf("expected similar documents to have distance <= %d, got %d", maxReasonableDistance, d)
}
}
// TestOracle validates the basic behaviour of the Oracle structure.
func TestOracle(t *testing.T) {
fpA := Fingerprint(strings.NewReader(htmlA), 3)
fpB := Fingerprint(strings.NewReader(htmlB), 3)
o := NewOracle()
if o.Seen(fpA, 0) {
t.Fatalf("oracle should not have seen fingerprint yet")
}
// Teach the oracle about fpA.
o.See(fpA)
if !o.Seen(fpA, 0) {
t.Fatalf("oracle should recognise an identical fingerprint once seen")
}
if o.Seen(fpB, 0) {
t.Fatalf("oracle should not treat different fingerprint as identical when r=0")
}
r := Distance(fpA, fpB)
if !o.Seen(fpB, r) {
t.Fatalf("oracle should recognise fingerprint within distance %d", r)
}
}
================================================
FILE: pkg/engine/headless/crawler/normalizer/text_utils.go
================================================
package normalizer
import (
"fmt"
"regexp"
"slices"
)
// DefaultTextPatterns is a list of regex patterns for the text normalizer
var DefaultTextPatterns = []string{
// emailAddress
`\b(?i)[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b`,
// ipAddress
`\b(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3}\b`,
// uuid
`\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b`,
// relativeDates
`\b(?:[0-9]{1,2}\s(?:days?|weeks?|months?|years?)\s(?:ago|from\s+now))\b`,
// priceAmounts (no leading \b due to currency symbols)
`[\$€£¥]\s*\d+(?:\.\d{1,2})?\b`,
// phoneNumbers
`\b\+?\d{7,15}\b`,
// ssnNumbers
`\b\d{3}-\d{2}-\d{4}\b`,
// timestampRegex
`\b(?:(?:[0-9]{4}-[0-9]{2}-[0-9]{2})|(?:(?:[0-9]{2}\/){2}[0-9]{4}))\s(?:[0-9]{2}:[0-9]{2}:[0-9]{2})\b`,
}
// TextNormalizer is a normalizer for text
type TextNormalizer struct {
// patterns is a list of regex patterns for the text normalizer
patterns []*regexp.Regexp
}
// NewTextNormalizer returns a new TextNormalizer
//
// patterns is a list of regex patterns for the text normalizer
// DefaultTextPatterns is used if patterns is nil. See DefaultTextPatterns for more info.
func NewTextNormalizer() (*TextNormalizer, error) {
patterns := slices.Clone(DefaultTextPatterns)
patterns = append(patterns, dateTimePatterns...)
var compiledPatterns []*regexp.Regexp
for _, pattern := range patterns {
pattern := pattern
compiledPattern, err := regexp.Compile(pattern)
if err != nil {
return nil, fmt.Errorf("error compiling pattern %s: %v", pattern, err)
}
compiledPatterns = append(compiledPatterns, compiledPattern)
}
return &TextNormalizer{patterns: compiledPatterns}, nil
}
// Apply applies the patterns to the text and returns the normalized text
func (n *TextNormalizer) Apply(text string) string {
for _, pattern := range n.patterns {
pattern := pattern
text = pattern.ReplaceAllString(text, "")
}
return text
}
================================================
FILE: pkg/engine/headless/crawler/normalizer/text_utils_test.go
================================================
package normalizer
import (
"strings"
"testing"
)
func TestTextNormalizer_AllPatterns(t *testing.T) {
normalizer, err := NewTextNormalizer()
if err != nil {
t.Fatalf("Failed to create normalizer: %v", err)
}
testText := `
Contact us at test@example.com or admin@SITE.ORG for support.
Server IP: 192.168.1.1 and public IP: 8.8.8.8
Invalid IPs should not match: 999.999.999.999 or 300.400.500.600
UUID: 550e8400-e29b-41d4-a716-446655440000
Relative dates: 5 days ago, 2 weeks from now, 10 months ago
Prices: $19.99, €50.00, £25.50, ¥1000
Phone numbers: +1234567890, +447911123456, +33123456789
SSN: 123-45-6789, 987-65-4321
Timestamps: 2023-12-25 14:30:00, 12/25/2023 09:15:30
`
result := normalizer.Apply(testText)
// Check that sensitive data was removed
testCases := []struct {
pattern string
shouldBeRemoved bool
description string
}{
{"test@example.com", true, "lowercase email"},
{"admin@SITE.ORG", true, "uppercase email"},
{"192.168.1.1", true, "private IP"},
{"8.8.8.8", true, "public IP"},
{"999.999.999.999", false, "invalid IP (too high octets)"},
{"300.400.500.600", false, "invalid IP (too high octets)"},
{"550e8400-e29b-41d4-a716-446655440000", true, "UUID"},
{"5 days ago", true, "relative date - days ago"},
{"2 weeks from now", true, "relative date - weeks from now"},
{"10 months ago", true, "relative date - months ago"},
{"$19.99", true, "USD price"},
{"€50.00", true, "EUR price"},
{"£25.50", true, "GBP price"},
{"¥1000", true, "JPY price"},
{"+1234567890", true, "international phone"},
{"+447911123456", true, "UK phone"},
{"+33123456789", true, "French phone"},
{"123-45-6789", true, "SSN format 1"},
{"987-65-4321", true, "SSN format 2"},
{"2023-12-25 14:30:00", true, "ISO timestamp"},
{"12/25/2023 09:15:30", true, "US timestamp"},
}
for _, tc := range testCases {
if tc.shouldBeRemoved {
if strings.Contains(result, tc.pattern) {
t.Errorf("%s: Pattern '%s' should have been removed but was found in result", tc.description, tc.pattern)
}
} else {
if !strings.Contains(result, tc.pattern) {
t.Errorf("%s: Invalid pattern '%s' should not have been removed but was not found in result", tc.description, tc.pattern)
}
}
}
t.Logf("Original text length: %d", len(testText))
t.Logf("Normalized text length: %d", len(result))
t.Logf("Normalized result: %s", result)
}
================================================
FILE: pkg/engine/headless/crawler/state.go
================================================
package crawler
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"log/slog"
"strings"
graphlib "github.com/dominikbraun/graph"
"github.com/pkg/errors"
"github.com/projectdiscovery/katana/pkg/engine/headless/browser"
"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/diagnostics"
"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/normalizer/simhash"
"github.com/projectdiscovery/katana/pkg/engine/headless/types"
)
var emptyPageHash = sha256Hash("")
const simhashThreshold = 2 // Allow up to 2 bits difference
func (c *Crawler) isCorrectNavigation(page *browser.BrowserPage, action *types.Action) (string, *types.PageState, error) {
currentPageHash, pageState, err := getPageHash(page)
if err != nil {
return "", nil, err
}
if currentPageHash == action.OriginID {
return currentPageHash, pageState, nil
}
// Get the origin page state to compare SimHash
originPageState, err := c.crawlGraph.GetPageState(action.OriginID)
if err != nil {
return "", pageState, fmt.Errorf("failed to get origin page state: %w", err)
}
if pageState != nil && originPageState != nil {
distance := simhash.Distance(pageState.SimHash, originPageState.SimHash)
if distance <= simhashThreshold {
c.logger.Debug("Page is similar enough to origin, proceeding",
slog.String("current_hash", currentPageHash),
slog.String("origin_hash", action.OriginID),
slog.Uint64("simhash_distance", uint64(distance)),
)
// Treat this page as the origin state to avoid creating a new vertex
return originPageState.UniqueID, pageState, nil
}
}
return "", pageState, fmt.Errorf("failed to navigate back to origin page: %s != %s", currentPageHash, action.OriginID)
}
func getPageHash(page *browser.BrowserPage) (string, *types.PageState, error) {
pageState, err := newPageState(page, nil)
if err == ErrEmptyPage {
return emptyPageHash, nil, nil
}
if err != nil {
return "", nil, errors.Wrap(err, "could not get page state")
}
return pageState.UniqueID, pageState, nil
}
var ErrEmptyPage = errors.New("page is empty")
func newPageState(page *browser.BrowserPage, action *types.Action) (*types.PageState, error) {
pageInfo, err := page.Info()
if err != nil {
return nil, errors.Wrap(err, "could not get page info")
}
if pageInfo.URL == "" || pageInfo.URL == "about:blank" {
return nil, ErrEmptyPage
}
outerHTML, err := page.HTML()
if err != nil {
return nil, errors.Wrap(err, "could not get html content")
}
state := &types.PageState{
URL: pageInfo.URL,
DOM: outerHTML,
NavigationAction: action,
Title: pageInfo.Title,
}
if action != nil {
state.Depth = action.Depth + 1
}
strippedDOM, err := getStrippedDOM(outerHTML)
if err != nil {
return nil, errors.Wrap(err, "could not get stripped dom")
}
state.StrippedDOM = strippedDOM
// Get sha256 hash of the stripped dom
state.UniqueID = sha256Hash(strippedDOM)
state.SimHash = simhash.Fingerprint(strings.NewReader(strippedDOM), 3)
return state, nil
}
func sha256Hash(item string) string {
hasher := sha256.New()
hasher.Write([]byte(item))
hashItem := hex.EncodeToString(hasher.Sum(nil))
return hashItem
}
func getStrippedDOM(contents string) (string, error) {
normalized, err := domNormalizer.Apply(contents)
if err != nil {
return "", errors.Wrap(err, "could not normalize dom")
}
return normalized, nil
}
var ErrNoNavigationPossible = errors.New("no navigation possible")
// navigateBackToStateOrigin implements the logic to navigate back to the state origin
//
// It implements different logics as an optimization to decide
// how to navigate back.
//
// 1. If the action has an element, check if the element is visible on the current page
// If the element is visible, directly use that to navigate.
//
// 2. If we have browser history, and the page is in the history which was the origin
// of the action, then we can directly use the browser history to navigate back.
//
// 3. If all else fails, we have the shortest path navigation.
func (c *Crawler) navigateBackToStateOrigin(action *types.Action, page *browser.BrowserPage, currentPageHash string) (string, error) {
c.logger.Debug("Found action with different origin id",
slog.String("action_origin_id", action.OriginID),
slog.String("current_page_hash", currentPageHash),
)
// Get vertex from the graph
originPageState, err := c.crawlGraph.GetPageState(action.OriginID)
if err != nil {
c.logger.Debug("Failed to get origin page state", slog.String("error", err.Error()))
return "", err
}
// First, check if the element we want to interact with exists on current page
if action.Element != nil && currentPageHash != emptyPageHash {
newPageHash, err := c.tryElementNavigation(page, action, currentPageHash)
if err != nil {
c.logger.Debug("Failed to navigate back to origin page using element", slog.String("error", err.Error()))
}
if newPageHash != "" {
return newPageHash, nil
}
}
// Try to see if we can move back using the browser history
newPageHash, err := c.tryBrowserHistoryNavigation(page, originPageState, action)
if err != nil {
c.logger.Debug("Failed to navigate back using browser history", slog.String("error", err.Error()))
}
if newPageHash != "" {
return newPageHash, nil
}
// Finally try Shortest path walking from root.
newPageHash, err = c.tryShortestPathNavigation(action, page, currentPageHash)
if err != nil {
return "", err
}
if newPageHash == "" {
return "", ErrNoNavigationPossible
}
return newPageHash, nil
}
func (c *Crawler) tryElementNavigation(page *browser.BrowserPage, action *types.Action, currentPageHash string) (string, error) {
element, err := page.ElementX(action.Element.XPath)
if err != nil {
return "", err
}
visible, err := element.Visible()
if err != nil {
return "", err
}
if !visible {
return "", nil
}
// Also ensure its interactable
interactable, err := element.Interactable()
if err != nil || interactable == nil {
return "", nil
}
// Ensure its the same element
htmlElement, err := page.GetElementFromXpath(action.Element.XPath)
if err != nil {
return "", err
}
// Ensure its the same element with stronger identity matching
if isElementMatch(htmlElement, action.Element) {
c.logger.Debug("Found target element on current page, proceeding without navigation")
// FIXME: Return the origin element ID so that the graph shows
// correctly the fastest way to reach the state.
return action.OriginID, nil
}
return "", nil
}
// isElementMatch implements stronger identity matching logic to reduce false positives.
// It treats identical ID as definitive match, otherwise requires both Classes and TextContent
// to match, or enforces at least two matching non-empty attributes.
func isElementMatch(current, target *types.HTMLElement) bool {
if current == nil || target == nil {
return false
}
// Definitive match: identical non-empty IDs
if current.ID != "" && target.ID != "" && current.ID == target.ID {
return true
}
matchCount := 0
if current.Classes != "" && target.Classes != "" && current.Classes == target.Classes {
matchCount++
}
if current.TextContent != "" && target.TextContent != "" && current.TextContent == target.TextContent {
matchCount++
}
if current.TagName != "" && target.TagName != "" && current.TagName == target.TagName {
matchCount++
}
// Require at least two matching non-empty attributes for a positive match
// This ensures stronger identity verification while still allowing reasonable fallbacks
return matchCount >= 2
}
func (c *Crawler) tryBrowserHistoryNavigation(page *browser.BrowserPage, originPageState *types.PageState, action *types.Action) (string, error) {
canNavigateBack, stepsBack, err := c.isBackNavigationPossible(page, originPageState)
if err != nil {
return "", err
}
if !canNavigateBack {
return "", nil
}
c.logger.Debug("Navigating back using browser history", slog.Int("steps_back", stepsBack))
var navigatedSuccessfully bool
for i := 0; i < stepsBack; i++ {
if err := page.NavigateBack(); err != nil {
return "", err
}
navigatedSuccessfully = true
}
if !navigatedSuccessfully {
return "", nil
}
if err := page.WaitPageLoadHeurisitics(); err != nil {
c.logger.Debug("Failed to wait for page load after navigating back using browser history", slog.String("error", err.Error()))
}
newPageHash, pageState, err := c.isCorrectNavigation(page, action)
if c.diagnostics != nil && pageState != nil {
if err := c.diagnostics.LogPageState(pageState, diagnostics.PreActionPageState); err != nil {
return "", err
}
}
if err != nil {
return "", err
}
return newPageHash, nil
}
func (c *Crawler) isBackNavigationPossible(page *browser.BrowserPage, originPage *types.PageState) (bool, int, error) {
history, err := page.GetNavigationHistory()
if err != nil {
return false, 0, err
}
if len(history.Entries) == 0 {
return false, 0, nil
}
currentIndex := history.CurrentIndex
for i, entry := range history.Entries {
if entry.URL == originPage.URL && originPage.Title == entry.Title {
stepsBack := currentIndex - i
return true, stepsBack, nil
}
}
return false, 0, nil
}
func (c *Crawler) tryShortestPathNavigation(action *types.Action, page *browser.BrowserPage, currentPageHash string) (string, error) {
c.logger.Debug("Trying Shortest path to navigate back to origin page", slog.String("action_origin_id", action.OriginID), slog.String("current_page_hash", currentPageHash))
actions, err := c.crawlGraph.ShortestPath(currentPageHash, action.OriginID)
if err != nil {
if errors.Is(err, graphlib.ErrTargetNotReachable) {
c.logger.Debug("Target not reachable, reaching from blank state",
slog.String("action_origin_id", action.OriginID),
)
actions, err = c.crawlGraph.ShortestPath(emptyPageHash, action.OriginID)
if err != nil {
return "", errors.Wrap(err, "could not find path to origin page")
}
} else {
return "", errors.Wrap(err, "failed to find shortest path")
}
}
c.logger.Debug("Found actions to traverse",
slog.Any("actions", actions),
)
for _, action := range actions {
if err := c.executeCrawlStateAction(action, page); err != nil {
return "", err
}
}
newPageHash, pageState, err := c.isCorrectNavigation(page, action)
if c.diagnostics != nil && pageState != nil {
if err := c.diagnostics.LogPageState(pageState, diagnostics.PreActionPageState); err != nil {
return "", err
}
}
if err != nil {
return "", err
}
return newPageHash, nil
}
================================================
FILE: pkg/engine/headless/crawler/state_test.go
================================================
package crawler
import (
"strings"
"testing"
"github.com/pkg/errors"
"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/normalizer/simhash"
"github.com/stretchr/testify/assert"
)
func TestPageFingerprint_Stability(t *testing.T) {
}
func TestPageFingerprint(t *testing.T) {
tests := []struct {
name string
html1 string
html2 string
shouldMatch bool
}{
{
name: "same page different dynamic content",
html1: `
Home
Welcome John!
Home
Profile
`,
html2: `
Home
Welcome Jane!
Home
Profile
`,
shouldMatch: true,
},
{
name: "same form different values",
html1: `
`,
html2: `
`,
shouldMatch: true,
},
{
name: "different error messages",
html1: `
Invalid password
`,
html2: `
Account locked
`,
shouldMatch: true,
},
{
name: "different page structure",
html1: `
`,
html2: `
`,
shouldMatch: false,
},
}
getHash := func(html string) (string, error) {
strippedDOM, err := getStrippedDOM(html)
if err != nil {
return "", errors.Wrap(err, "could not get stripped dom")
}
// Get sha256 hash of the stripped dom
return sha256Hash(strippedDOM), nil
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hash1, err := getHash(tt.html1)
assert.NoError(t, err)
hash2, err := getHash(tt.html2)
assert.NoError(t, err)
if tt.shouldMatch {
assert.Equal(t, hash1, hash2)
} else {
assert.NotEqual(t, hash1, hash2)
}
})
}
}
func TestSimHashSimilarity(t *testing.T) {
tests := []struct {
name string
html1 string
html2 string
threshold uint8
similar bool
}{
{
name: "identical pages",
html1: `Hello World Content here
`,
html2: `Hello World Content here
`,
threshold: 4,
similar: true,
},
{
name: "pages with minor changes",
html1: `Hello World Content here
Time: 12:00 `,
html2: `Hello World Content here
Time: 12:01 `,
threshold: 4,
similar: true,
},
{
name: "pages with different content",
html1: `Hello World Content here
`,
html2: `Goodbye World Different content
Extra stuff
`,
threshold: 4,
similar: false,
},
{
name: "pages with dynamic IDs",
html1: `
Hello `,
html2: `
Hello `,
threshold: 4,
similar: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Normalize and compute SimHash
norm1, err := domNormalizer.Apply(tt.html1)
if err != nil {
t.Fatalf("Failed to normalize html1: %v", err)
}
norm2, err := domNormalizer.Apply(tt.html2)
if err != nil {
t.Fatalf("Failed to normalize html2: %v", err)
}
hash1 := simhash.Fingerprint(strings.NewReader(norm1), 3)
hash2 := simhash.Fingerprint(strings.NewReader(norm2), 3)
distance := simhash.Distance(hash1, hash2)
isSimilar := distance <= tt.threshold
if isSimilar != tt.similar {
t.Errorf("Expected similar=%v, got similar=%v (distance=%d)", tt.similar, isSimilar, distance)
t.Logf("Hash1: %064b", hash1)
t.Logf("Hash2: %064b", hash2)
t.Logf("Normalized HTML1:\n%s", norm1)
t.Logf("Normalized HTML2:\n%s", norm2)
}
})
}
}
================================================
FILE: pkg/engine/headless/debugger.go
================================================
package headless
import (
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
)
// ActiveURL represents a URL currently being processed
type ActiveURL struct {
URL string `json:"url"`
StartTime time.Time `json:"start_time"`
Duration string `json:"duration"`
Depth int `json:"depth"`
}
// CrawlDebugger tracks active URLs for debugging
type CrawlDebugger struct {
mu sync.RWMutex
activeURLs map[string]*ActiveURL
httpServer *http.Server
}
// NewCrawlDebugger creates a new debugger instance
func NewCrawlDebugger(httpPort int) *CrawlDebugger {
cd := &CrawlDebugger{
activeURLs: make(map[string]*ActiveURL),
}
mux := http.NewServeMux()
mux.HandleFunc("/debug/active-urls", cd.handleActiveURLs)
mux.HandleFunc("/debug/health", cd.handleHealth)
cd.httpServer = &http.Server{
Addr: fmt.Sprintf("127.0.0.1:%d", httpPort),
Handler: mux,
ReadTimeout: 5 * time.Second,
ReadHeaderTimeout: 2 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 30 * time.Second,
}
go func() {
if err := cd.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("Debug HTTP server error: %v\n", err)
}
}()
return cd
}
// StartURL marks a URL as being processed
func (cd *CrawlDebugger) StartURL(url string, depth int) {
if cd == nil {
return
}
cd.mu.Lock()
cd.activeURLs[url] = &ActiveURL{
URL: url,
StartTime: time.Now(),
Depth: depth,
}
cd.mu.Unlock()
}
// EndURL marks a URL as finished processing
func (cd *CrawlDebugger) EndURL(url string) {
if cd == nil {
return
}
cd.mu.Lock()
delete(cd.activeURLs, url)
cd.mu.Unlock()
}
// GetActiveURLs returns currently active URLs with durations
func (cd *CrawlDebugger) GetActiveURLs() []ActiveURL {
if cd == nil {
return nil
}
cd.mu.RLock()
defer cd.mu.RUnlock()
urls := make([]ActiveURL, 0, len(cd.activeURLs))
now := time.Now()
for _, au := range cd.activeURLs {
copy := *au
copy.Duration = now.Sub(au.StartTime).String()
urls = append(urls, copy)
}
return urls
}
// HTTP handlers
func (cd *CrawlDebugger) handleActiveURLs(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
active := cd.GetActiveURLs()
if err := json.NewEncoder(w).Encode(map[string]interface{}{
"timestamp": time.Now().Format(time.RFC3339),
"active_urls": active,
"count": len(active),
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (cd *CrawlDebugger) handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(map[string]interface{}{
"status": "ok",
"timestamp": time.Now().Format(time.RFC3339),
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (cd *CrawlDebugger) Close() {
if cd == nil {
return
}
if cd.httpServer != nil {
_ = cd.httpServer.Close()
}
}
================================================
FILE: pkg/engine/headless/graph/graph.go
================================================
// Package graph implements a Directed Graph for storing
// state information during crawling of a Web Application.
package graph
import (
"os"
"github.com/dominikbraun/graph"
"github.com/dominikbraun/graph/draw"
"github.com/pkg/errors"
"github.com/projectdiscovery/katana/pkg/engine/headless/types"
)
// CrawlGraph is a graph for storing state information during crawling
type CrawlGraph struct {
graph graph.Graph[string, types.PageState]
}
func navigationHasherFunc(n types.PageState) string {
return n.UniqueID
}
// NewCrawlGraph creates a new CrawlGraph instance
func NewCrawlGraph() *CrawlGraph {
return &CrawlGraph{
graph: graph.New(navigationHasherFunc, func(t *graph.Traits) {
t.IsDirected = true
t.IsRooted = true
t.IsWeighted = true
}),
}
}
func (g *CrawlGraph) GetVertices() []string {
vertices := []string{}
adjacencyMap, err := g.graph.AdjacencyMap()
if err != nil {
return nil
}
for vertex := range adjacencyMap {
vertices = append(vertices, vertex)
}
return vertices
}
// AddNavigation adds a navigation to the graph
func (g *CrawlGraph) AddPageState(n types.PageState) error {
vertexAttrs := map[string]string{
"label": n.URL,
}
if n.IsRoot {
vertexAttrs["is_root"] = "true"
}
err := g.graph.AddVertex(n, func(vp *graph.VertexProperties) {
vp.Weight = n.Depth
vp.Attributes = vertexAttrs
})
if err != nil {
if errors.Is(err, graph.ErrVertexAlreadyExists) {
return nil
}
return errors.Wrap(err, "could not add vertex to graph")
}
if n.NavigationAction != nil {
edgeAttrs := map[string]string{
"label": n.NavigationAction.String(),
}
err = g.graph.AddEdge(n.OriginID, n.UniqueID, func(ep *graph.EdgeProperties) {
ep.Weight = n.Depth
ep.Attributes = edgeAttrs
})
if err != nil {
if errors.Is(err, graph.ErrEdgeAlreadyExists) {
return nil
}
return errors.Wrapf(err, "could not add edge to graph: source vertex %s", n.OriginID)
}
}
return nil
}
func (g *CrawlGraph) AddEdge(sourceState, targetState string, action *types.Action) error {
if action == nil {
return errors.New("add edge: action cannot be nil")
}
edgeAttrs := map[string]string{
"label": action.String(),
}
err := g.graph.AddEdge(sourceState, targetState, func(ep *graph.EdgeProperties) {
ep.Weight = action.Depth
ep.Attributes = edgeAttrs
})
if err != nil {
if errors.Is(err, graph.ErrEdgeAlreadyExists) {
return nil
}
return errors.Wrap(err, "could not add edge to graph")
}
return nil
}
func (g *CrawlGraph) GetPageState(id string) (*types.PageState, error) {
pageVertex, err := g.graph.Vertex(id)
if err != nil {
return nil, errors.Wrap(err, "could not get vertex")
}
return &pageVertex, nil
}
func (g *CrawlGraph) ShortestPath(sourceState, targetState string) ([]*types.Action, error) {
shortestPath, err := graph.ShortestPath(g.graph, sourceState, targetState)
if err != nil {
return nil, errors.Wrap(err, "could not find shortest path")
}
actionsSlice := make([]*types.Action, 0, len(shortestPath))
for _, path := range shortestPath {
pageVertex, err := g.graph.Vertex(path)
if err != nil {
return nil, errors.Wrap(err, "could not get vertex")
}
if pageVertex.URL == "about:blank" || pageVertex.NavigationAction == nil {
continue
}
actionsSlice = append(actionsSlice, pageVertex.NavigationAction)
}
return actionsSlice, nil
}
func (g *CrawlGraph) DrawGraph(file string) error {
f, err := os.Create(file)
if err != nil {
return errors.Wrap(err, "could not create graph file")
}
defer func() { _ = f.Close() }()
return draw.DOT(g.graph, f)
}
================================================
FILE: pkg/engine/headless/headless.go
================================================
package headless
import (
"log/slog"
"net/url"
"os"
"time"
"github.com/lmittmann/tint"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/engine/headless/browser"
"github.com/projectdiscovery/katana/pkg/engine/headless/captcha"
_ "github.com/projectdiscovery/katana/pkg/engine/headless/captcha/capsolver"
"github.com/projectdiscovery/katana/pkg/engine/headless/crawler"
"github.com/projectdiscovery/katana/pkg/engine/parser"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/types"
"github.com/projectdiscovery/katana/pkg/utils"
mapsutil "github.com/projectdiscovery/utils/maps"
)
type Headless struct {
logger *slog.Logger
options *types.CrawlerOptions
deduplicator *mapsutil.SyncLockMap[string, struct{}]
pathTrie *utils.PathTrie
debugger *CrawlDebugger
}
// New returns a new headless crawler instance
func New(options *types.CrawlerOptions) (*Headless, error) {
logger := newLogger(options)
headless := &Headless{
logger: logger,
options: options,
deduplicator: mapsutil.NewSyncLockMap[string, struct{}](),
}
if options.Options.FilterSimilar {
headless.pathTrie = utils.NewPathTrie(options.Options.FilterSimilarThreshold)
}
// Show crawl debugger if verbose is enabled
if options.Options.Verbose {
headless.debugger = NewCrawlDebugger(8089)
}
return headless, nil
}
func newLogger(options *types.CrawlerOptions) *slog.Logger {
if options.Logger != nil {
return options.Logger
}
writer := os.Stderr
// set global logger with custom options
level := slog.LevelInfo
if options.Options.Debug {
level = slog.LevelDebug
}
logger := slog.New(
tint.NewHandler(writer, &tint.Options{
Level: level,
TimeFormat: time.Kitchen,
}),
)
return logger
}
func validateScopeFunc(h *Headless, URL string) browser.ScopeValidator {
parsedURL, err := url.Parse(URL)
if err != nil {
return func(string) bool { return true }
}
rootHostname := parsedURL.Hostname()
return func(s string) bool {
if h.options.ScopeManager == nil {
return true
}
parsed, err := url.Parse(s)
if err != nil {
return false
}
validated, err := h.options.ScopeManager.Validate(parsed, rootHostname)
if err != nil {
return false
}
return validated
}
}
// Crawl executes the headless crawling on a given URL
func (h *Headless) Crawl(URL string) error {
if h.debugger != nil {
h.debugger.StartURL(URL, 0)
}
defer func() {
if h.debugger != nil {
h.debugger.EndURL(URL)
}
}()
scopeValidator := validateScopeFunc(h, URL)
crawlOpts := crawler.Options{
ChromiumPath: h.options.Options.SystemChromePath,
MaxDepth: h.options.Options.MaxDepth,
ShowBrowser: h.options.Options.ShowBrowser,
MaxCrawlDuration: h.options.Options.CrawlDuration,
MaxFailureCount: h.options.Options.MaxFailureCount,
NoSandbox: h.options.Options.HeadlessNoSandbox,
Proxy: h.options.Options.Proxy,
MaxBrowsers: 1,
PageMaxTimeout: 30 * time.Second,
ScopeValidator: scopeValidator,
AutomaticFormFill: h.options.Options.AutomaticFormFill,
RequestCallback: func(rr *output.Result) {
if rr == nil || rr.Request == nil {
return
}
if scopeValidator != nil && !scopeValidator(rr.Request.URL) {
return
}
navigationRequests := h.performAdditionalAnalysis(rr)
for _, req := range navigationRequests {
if err := h.options.OutputWriter.Write(req); err != nil {
h.logger.Debug("failed to write navigation result",
slog.String("url", func() string {
if req != nil && req.Request != nil {
return req.Request.URL
}
return ""
}()),
slog.String("error", err.Error()),
)
}
}
if rr.Response != nil {
rr.Response.KnowledgeBase = h.options.ClassifyPage(rr.Response.Body)
rr.Response.Raw = ""
rr.Response.Body = ""
}
if err := h.options.OutputWriter.Write(rr); err != nil {
h.logger.Debug("failed to write result",
slog.String("error", err.Error()),
)
}
},
Logger: h.logger,
ChromeUser: h.options.ChromeUser,
EnableDiagnostics: h.options.Options.EnableDiagnostics,
Trace: h.options.Options.EnableDiagnostics,
CookieConsentBypass: true,
}
if provider := h.options.Options.CaptchaSolverProvider; provider != "" {
gologger.Debug().Msgf("captcha solver enabled: provider=%s", provider)
handler, err := captcha.NewHandler(provider, h.options.Options.CaptchaSolverAPIKey)
if err != nil {
gologger.Warning().Msgf("captcha handler init failed: %s", err)
} else {
crawlOpts.CaptchaHandler = handler
}
}
// TODO: Make the crawling multi-threaded. Right now concurrency is hardcoded to 1.
headlessCrawler, err := crawler.New(crawlOpts)
if err != nil {
return err
}
defer headlessCrawler.Close()
if err = headlessCrawler.Crawl(URL); err != nil {
return err
}
return nil
}
func (h *Headless) Close() error {
if h.debugger != nil {
h.debugger.Close()
}
return nil
}
func (h *Headless) performAdditionalAnalysis(rr *output.Result) []*output.Result {
responseParser := parser.NewResponseParser()
newNavigations := responseParser.ParseResponse(rr.Response)
navigationRequests := make([]*output.Result, 0)
for _, resp := range newNavigations {
dedupKey := resp.URL
if h.options.Options.FilterSimilar {
dedupKey = utils.FingerprintURL(dedupKey, h.pathTrie)
}
if _, ok := h.deduplicator.Get(dedupKey); ok {
continue
}
if err := h.deduplicator.Set(dedupKey, struct{}{}); err != nil {
h.logger.Debug("deduplicator set failed",
slog.String("url", resp.URL),
slog.String("error", err.Error()),
)
continue
}
navigationRequests = append(navigationRequests, &output.Result{
Request: resp,
})
}
return navigationRequests
}
================================================
FILE: pkg/engine/headless/js/js.go
================================================
package js
import (
_ "embed"
"github.com/go-rod/rod"
"github.com/pkg/errors"
)
var (
//go:embed utils.js
utilsJavascriptBundle string
//go:embed page-init.js
pageInitJavascriptBundle string
)
// InitJavascriptEnv injects the necessary javascript code into the browser
func InitJavascriptEnv(page *rod.Page) error {
if _, err := page.EvalOnNewDocument(utilsJavascriptBundle); err != nil {
return errors.Wrap(err, "failed to inject utils.js")
}
if _, err := page.EvalOnNewDocument(pageInitJavascriptBundle); err != nil {
return errors.Wrap(err, "failed to inject page-init.js")
}
return nil
}
================================================
FILE: pkg/engine/headless/js/page-init.js
================================================
// This script initializes the page and hooks up event listeners
// and other interesting stuff needed to make the crawling work.
//
// Actions performed:
//
// 1. Hook addTargetListener to capture all the event listeners added on the page.
// These are accessible via window.__eventListeners
// 2. Hook window.open to capture all the opened pages.
// These are accessible via window.__navigatedLinks
// 3. Hook setTimeout and setInterval to speed up delayed actions
// 4. Hook form reset to prevent the form from being reset
// 5. Hook window.close to prevent the page from being closed
// 6. Hook history pushState and replaceState for new links
// 7. Add event listener for hashchange to identify new navigations
// 8. TODO: Hook inline event listeners so that layer0 event listeners can be tracked as well
(function pageInitAndHook() {
const markElementReadonlyProperties = {
writable: false,
configurable: false,
};
// hookNavigatedLinkSinks hooks the navigated link sinks
// on the page to capture all the navigated links.
function hookNavigatedLinkSinks() {
window.__navigatedLinks = [];
// Hook history.pushState and history.replaceState to capture all the navigated links
const __origPushState = window.history.pushState.bind(window.history);
const __origReplaceState = window.history.replaceState.bind(window.history);
function __wrappedPushState(a, b, c) {
try { window.__navigatedLinks.push({ url: c, source: "history.pushState" }); } catch (_) {}
return __origPushState(a, b, c);
}
function __wrappedReplaceState(a, b, c) {
try { window.__navigatedLinks.push({ url: c, source: "history.replaceState" }); } catch (_) {}
return __origReplaceState(a, b, c);
}
Object.defineProperty(window.history, "pushState", { value: __wrappedPushState, writable: false, configurable: false });
Object.defineProperty(window.history, "replaceState", { value: __wrappedReplaceState, writable: false, configurable: false });
// Hook window.open to capture all the opened pages
const __origOpen = window.open.bind(window);
function __wrappedOpen(url, ...rest) {
try { window.__navigatedLinks.push({ url, source: "window.open" }); } catch (_) {}
return __origOpen(url, ...rest);
}
Object.defineProperty(window, "open", { value: __wrappedOpen, writable: false, configurable: false });
// Add event listener for hashchange
window.addEventListener("hashchange", function () {
window.__navigatedLinks.push({
url: document.location.href,
source: "hashchange",
});
});
const __OrigWebSocket = window.WebSocket;
function __WrappedWebSocket(url, protocols) {
try { window.__navigatedLinks.push({ url, source: "websocket" }); } catch (_) {}
return Reflect.construct(__OrigWebSocket, [url, protocols], new.target || __WrappedWebSocket);
}
__WrappedWebSocket.prototype = __OrigWebSocket.prototype;
Object.setPrototypeOf(__WrappedWebSocket, __OrigWebSocket);
Object.defineProperty(window, "WebSocket", { value: __WrappedWebSocket, writable: false, configurable: false });
const __OrigEventSource = window.EventSource;
function __WrappedEventSource(url, eventSourceInitDict) {
try { window.__navigatedLinks.push({ url, source: "eventsource" }); } catch (_) {}
return Reflect.construct(__OrigEventSource, [url, eventSourceInitDict], new.target || __WrappedEventSource);
}
__WrappedEventSource.prototype = __OrigEventSource.prototype;
Object.setPrototypeOf(__WrappedEventSource, __OrigEventSource);
Object.defineProperty(window, "EventSource", { value: __WrappedEventSource, writable: false, configurable: false });
const __origFetch = window.fetch.bind(window);
function __wrappedFetch(...args) {
const url = args[0] instanceof Request ? args[0].url : args[0];
try { window.__navigatedLinks.push({ url: url, source: "fetch" }); } catch (_) {}
return __origFetch(...args);
}
Object.defineProperty(window, "fetch", { value: __wrappedFetch, writable: false, configurable: false });
}
// hookMiscellaneousUtilities performs miscellaneous hooks
// on the page to prevent certain actions from happening
// and to speed up certain actions.
function hookMiscellaneousUtilities() {
// Hook form reset to conditionally prevent the form from being reset
const __origFormReset = HTMLFormElement.prototype.reset;
const __wrappedFormReset = function (...args) {
if (window.__katanaHooksOptions?.preventFormReset === true) {
try { console.log("[hook] cancel reset form"); } catch (_) {}
return;
}
return __origFormReset.apply(this, args);
};
Object.defineProperty(
HTMLFormElement.prototype,
"reset",
{ value: __wrappedFormReset, writable: false, configurable: false }
);
// Hook window.close to prevent the page from being closed
const __wrappedClose = function () {
console.log("[hook] trying to close page.");
};
Object.defineProperty(window, "close", { value: __wrappedClose, writable: false, configurable: false });
// Hook setTimeout and setInterval to speed up delayed actions
// on the page. This is useful where there is some request happening
// on the page after a delay or some animation happening after a delay.
const __origSetTimeout = window.setTimeout;
const __origSetInterval = window.setInterval;
const speedUpFactor = 0.1;
function __wrappedSetTimeout(callback, delay, ...args) {
return __origSetTimeout(callback, delay * speedUpFactor, ...args);
}
function __wrappedSetInterval(callback, delay, ...args) {
return __origSetInterval(callback, delay * speedUpFactor, ...args);
}
Object.defineProperty(window, "setTimeout", { value: __wrappedSetTimeout, writable: false, configurable: false });
Object.defineProperty(window, "setInterval", { value: __wrappedSetInterval, writable: false, configurable: false });
}
// hookAddEventListener hooks the addTargetListener to capture
// all the event listeners added on the page
function hookAddEventListener() {
const originalAddEventListener = Element.prototype.addEventListener;
window.__eventListeners = [];
Element.prototype.addEventListener = function (type, listener, options) {
// Ensure `this` is a valid element and has the necessary properties
if (!this || !this.tagName) {
return originalAddEventListener.call(this, type, listener, options);
}
if (this.tagName == "BODY") {
return originalAddEventListener.call(this, type, listener, options);
}
let item = {
element: {
tagName: this.tagName,
id: this.id,
classes: this.className,
outerHTML: this.outerHTML.slice(0, 100), // Capture a snippet of the element's outerHTML
xpath: window.getXPath(this),
cssSelector: window.getCssPath(this),
attributes: window.getElementAttributes(this),
textContent: this.textContent.trim(),
hidden: this.hidden,
name: this.name,
type: this.type,
value: this.value,
},
type: type,
listener: listener.toString(),
options: options || {},
};
console.log("[hook] got event listener", item);
window.__eventListeners.push(item);
return originalAddEventListener.call(this, type, listener, options);
};
}
// Main hook initialization part
const __opts = window.__katanaHooksOptions || { hooked: false };
try { if (__opts.hooked === true) hookAddEventListener(); } catch (_) {}
try { if (__opts.hooked === true) hookNavigatedLinkSinks(); } catch (_) {}
try { if (__opts.hooked === true) hookMiscellaneousUtilities(); } catch (_) {}
})();
================================================
FILE: pkg/engine/headless/js/utils.js
================================================
// This file contains utility JS functions that are utilised by
// the main crawling JS code to perform actions.
(function initUtilityFunctions() {
// getElementAttributes returns the attributes of an element
window.getElementAttributes = function (element) {
const attrs = {};
for (let attr of element.attributes) {
attrs[attr.name] = attr.value;
}
return attrs;
};
// _elementDataFromElement returns the data for an element
window._elementDataFromElement = function (el) {
return {
tagName: el.tagName,
id: el.id,
classes: typeof el.className === 'string' ? el.className : Array.from(el.classList).join(' '),
attributes: window.getElementAttributes(el),
hidden: el.hidden,
outerHTML: el.outerHTML,
name: el.name,
type: el.type,
value: el.value != null ? String(el.value) : '',
textContent: el.textContent.trim(),
xpath: window.getXPath(el),
cssSelector: window.getCssPath(el),
};
};
// getAllElements returns all the elements for a query
// selector on the page
window.getAllElements = function (selector) {
try {
const nodes = document.querySelectorAll(selector);
return Array.from(nodes).map((el) => _elementDataFromElement(el));
} catch (_) {
return [];
}
};
window.getElementFromXPath = function (xpath) {
try {
const element = document
.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)
.singleNodeValue;
return element ? _elementDataFromElement(element) : null;
} catch (_) {
return null;
}
}
// getAllElementsWithEventListeners returns all the elements
// on the page along with their event listeners
// TODO: Is it optimized? or do we need to do something else?
window.getAllElementsWithEventListeners = function () {
const elements = document.querySelectorAll("*");
const elementsWithListeners = [];
for (let el of elements) {
const listeners = getEventListeners(el);
if (listeners && listeners.length) {
elementsWithListeners.push({
element: _elementDataFromElement(el),
listeners: listeners,
});
}
}
return elementsWithListeners;
};
// getEventListeners returns all the event listeners
// attached to an element
function getEventListeners(element) {
const listeners = [];
for (let event in element) {
if (event.startsWith("on")) {
const listener = element[event];
if (typeof listener === "function") {
listeners.push({
type: event,
listener: listener.toString(),
});
}
}
}
return listeners;
}
// getAllForms returns all the forms on the page
// along with their elements
window.getAllForms = function () {
const forms = document.querySelectorAll("form");
const pseudoForms = document.querySelectorAll("div.form");
const allForms = [...forms, ...pseudoForms];
return Array.from(allForms).map((form) => ({
tagName: form.tagName,
id: form.id,
classes: typeof form.className === 'string' ? form.className : Array.from(form.classList).join(' '),
attributes: window.getElementAttributes(form),
outerHTML: form.outerHTML,
action: form.action ? String(form.action) : '',
method: form.method,
xpath: window.getXPath(form),
cssSelector: window.getCssPath(form),
elements: form.elements ?
Array.from(form.elements).map((el) => _elementDataFromElement(el)) :
Array.from(form.querySelectorAll('input, select, textarea, button')).map((el) => _elementDataFromElement(el))
}));
};
// Copyright (C) Chrome Authors
// The below code is part of the Chrome DevTools project
// and is adapted from there.
// Utility to get the CSS selector path for an element.
window.getCssPath = function (node, optimized = false) {
if (node.nodeType !== Node.ELEMENT_NODE) return "";
const steps = [];
let contextNode = node;
while (contextNode) {
const step = window._cssPathStep(
contextNode,
optimized,
contextNode === node
);
if (!step) break; // Error - bail out early.
steps.push(step.value);
if (step.optimized) break;
contextNode = contextNode.parentNode;
}
steps.reverse();
return steps.join(" > ");
};
// Utility to get the XPath for an element.
window.getXPath = function (node, optimized = false) {
if (node.nodeType === Node.DOCUMENT_NODE) return "/";
const steps = [];
let contextNode = node;
while (contextNode) {
const step = window._xPathValue(contextNode, optimized);
if (!step) break; // Error - bail out early.
steps.push(step.value);
if (step.optimized) break;
contextNode = contextNode.parentNode;
}
steps.reverse();
return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/");
};
// Helper to create a step in the CSS path.
window._cssPathStep = function (node, optimized, isTargetNode) {
if (node.nodeType !== Node.ELEMENT_NODE) return null;
const id = node.getAttribute("id");
if (optimized) {
if (id) return { value: `#${id}`, optimized: true };
const nodeNameLower = node.nodeName.toLowerCase();
if (
nodeNameLower === "body" ||
nodeNameLower === "head" ||
nodeNameLower === "html"
)
return { value: node.nodeName, optimized: true };
}
const nodeName = node.nodeName;
if (id) return { value: `${nodeName}#${id}`, optimized: true };
const parent = node.parentNode;
if (!parent || parent.nodeType === Node.DOCUMENT_NODE)
return { value: nodeName, optimized: true };
const prefixedOwnClassNamesArray = window.prefixedElementClassNames(node);
let needsClassNames = false;
let needsNthChild = false;
let ownIndex = -1;
let elementIndex = -1;
const siblings = parent.children;
for (
let i = 0;
(ownIndex === -1 || !needsNthChild) && i < siblings.length;
++i
) {
const sibling = siblings[i];
if (sibling.nodeType !== Node.ELEMENT_NODE) continue;
elementIndex += 1;
if (sibling === node) {
ownIndex = elementIndex;
continue;
}
if (needsNthChild) continue;
if (sibling.nodeName.toLowerCase() !== nodeName.toLowerCase()) continue;
needsClassNames = true;
const ownClassNames = new Set(prefixedOwnClassNamesArray);
if (!ownClassNames.size) {
needsNthChild = true;
continue;
}
const siblingClassNamesArray = window.prefixedElementClassNames(sibling);
for (let j = 0; j < siblingClassNamesArray.length; ++j) {
const siblingClass = siblingClassNamesArray[j];
if (!ownClassNames.has(siblingClass)) continue;
ownClassNames.delete(siblingClass);
if (!ownClassNames.size) {
needsNthChild = true;
break;
}
}
}
let result = nodeName;
if (
isTargetNode &&
nodeName.toLowerCase() === "input" &&
node.getAttribute("type") &&
!node.getAttribute("id") &&
!node.getAttribute("class")
)
result += '[type="' + node.getAttribute("type") + '"]';
if (needsNthChild) {
result += `:nth-child(${ownIndex + 1})`;
} else if (needsClassNames) {
for (const prefixedName of prefixedOwnClassNamesArray)
result += "." + window.escapeIdentifierIfNeeded(prefixedName.substr(1));
}
return { value: result, optimized: false };
};
// Helper to get class names prefixed with '$' for mapping purposes.
window.prefixedElementClassNames = function (node) {
const classAttribute = node.getAttribute("class");
if (!classAttribute) return [];
return classAttribute
.split(/\s+/g)
.filter(Boolean)
.map(function (name) {
return "$" + name;
});
};
// Helper to escape identifiers for use in CSS selectors.
window.escapeIdentifierIfNeeded = function (ident) {
if (window.isCSSIdentifier(ident)) return ident;
const shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident);
const lastIndex = ident.length - 1;
return ident.replace(/./g, function (c, i) {
return (shouldEscapeFirst && i === 0) || !window.isCSSIdentChar(c)
? window.escapeAsciiChar(c, i === lastIndex)
: c;
});
};
// Helper to determine if a character is valid in a CSS identifier.
window.isCSSIdentChar = function (c) {
if (/[a-zA-Z0-9_-]/.test(c)) return true;
return c.charCodeAt(0) >= 0xa0;
};
// Helper to determine if a string is a valid CSS identifier.
window.isCSSIdentifier = function (value) {
return /^-{0,2}[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value);
};
// Helper to escape ASCII characters for use in CSS selectors.
window.escapeAsciiChar = function (c, isLast) {
return (
"\\" + c.charCodeAt(0).toString(16).padStart(2, "0") + (isLast ? "" : " ")
);
};
// Helper to get the XPath step for a node.
window._xPathValue = function (node, optimized) {
let ownValue;
const ownIndex = window._xPathIndex(node);
if (ownIndex === -1) return null;
switch (node.nodeType) {
case Node.ELEMENT_NODE:
if (optimized && node.getAttribute("id"))
return {
value: `//*[@id="${node.getAttribute("id")}"]`,
optimized: true,
};
ownValue = node.localName;
break;
case Node.ATTRIBUTE_NODE:
ownValue = "@" + node.nodeName;
break;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
ownValue = "text()";
break;
case Node.PROCESSING_INSTRUCTION_NODE:
ownValue = "processing-instruction()";
break;
case Node.COMMENT_NODE:
ownValue = "comment()";
break;
case Node.DOCUMENT_NODE:
ownValue = "";
break;
default:
ownValue = "";
break;
}
if (ownIndex > 0) ownValue += `[${ownIndex}]`;
return { value: ownValue, optimized: node.nodeType === Node.DOCUMENT_NODE };
};
// Helper to get the XPath index for a node.
window._xPathIndex = function (node) {
function areNodesSimilar(left, right) {
if (left === right) return true;
if (
left.nodeType === Node.ELEMENT_NODE &&
right.nodeType === Node.ELEMENT_NODE
)
return left.localName === right.localName;
if (left.nodeType === right.nodeType) return true;
const leftType =
left.nodeType === Node.CDATA_SECTION_NODE
? Node.TEXT_NODE
: left.nodeType;
const rightType =
right.nodeType === Node.CDATA_SECTION_NODE
? Node.TEXT_NODE
: right.nodeType;
return leftType === rightType;
}
const siblings = node.parentNode ? node.parentNode.children : null;
if (!siblings) return 0;
let hasSameNamedElements = false;
for (let i = 0; i < siblings.length; ++i) {
if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {
hasSameNamedElements = true;
break;
}
}
if (!hasSameNamedElements) return 0;
let ownIndex = 1;
for (let i = 0; i < siblings.length; ++i) {
if (areNodesSimilar(node, siblings[i])) {
if (siblings[i] === node) return ownIndex;
++ownIndex;
}
}
return -1;
};
})();
================================================
FILE: pkg/engine/headless/types/types.go
================================================
package types
import (
"crypto/md5"
"encoding/hex"
"fmt"
"os"
"sort"
"strings"
)
var (
IsDiagnosticEnabled = false
)
// PageState represents a page in the state of the
// web application as determined by the crawler.
// It represents the vertex of the crawl graph
type PageState struct {
UniqueID string `json:"unique_id,omitempty"`
SimHash uint64 `json:"sim_hash,omitempty"`
OriginID string `json:"origin_id,omitempty"`
URL string `json:"url,omitempty"`
Title string `json:"title,omitempty"`
DOM string `json:"dom,omitempty"`
StrippedDOM string `json:"stripped_dom,omitempty"`
Depth int `json:"depth,omitempty"`
IsRoot bool `json:"is_root,omitempty"`
// NavigationAction is actions taken to reach this state
NavigationAction *Action `json:"navigation_actions,omitempty"`
}
// Action is a action taken in the browser
type Action struct {
OriginID string `json:"origin_id,omitempty"`
Type ActionType `json:"type,omitempty"`
Input string `json:"input,omitempty"`
Element *HTMLElement `json:"element,omitempty"`
Form *HTMLForm `json:"form,omitempty"`
Depth int `json:"depth,omitempty"`
ResultID string `json:"result_id,omitempty"`
}
func (a *Action) Hash() string {
if a.Element != nil {
return a.Element.Hash()
}
if a.Form != nil {
return a.Form.Hash()
}
return string(a.Type) + "|" + a.Input + "|" + a.OriginID
}
func (a *Action) String() string {
var builder strings.Builder
builder.WriteString(string(a.Type))
if a.Type == ActionTypeLoadURL {
fmt.Fprintf(&builder, " %s", a.Input)
}
if a.Element != nil {
fmt.Fprintf(&builder, " on %s", a.Element)
}
value := builder.String()
return value
}
type ActionType string
const (
ActionTypeUnknown ActionType = "unknown"
ActionTypeLoadURL ActionType = "load_url"
ActionTypeExecuteJS ActionType = "execute_js"
ActionTypeLeftClick ActionType = "left_click"
ActionTypeLeftClickDown ActionType = "left_click_down"
ActionTypeLeftClickUp ActionType = "left_click_up"
ActionTypeRightClick ActionType = "right_click"
ActionTypeDoubleClick ActionType = "double_click"
ActionTypeScroll ActionType = "scroll"
ActionTypeSendKeys ActionType = "send_keys"
ActionTypeKeyUp ActionType = "key_up"
ActionTypeKeyDown ActionType = "key_down"
ActionTypeHover ActionType = "hover"
ActionTypeFocus ActionType = "focus"
ActionTypeBlur ActionType = "blur"
ActionTypeMouseOverAndOut ActionType = "mouse_over_and_out"
ActionTypeMouseWheel ActionType = "mouse_wheel"
ActionTypeFillForm ActionType = "fill_form"
ActionTypeWait ActionType = "wait"
ActionTypeRedirect ActionType = "redirect"
ActionTypeSubRequest ActionType = "sub_request"
)
func ActionFromEventListener(listener *EventListener) *Action {
var actionType ActionType
switch listener.Type {
case "click":
actionType = ActionTypeLeftClick
case "mousedown":
actionType = ActionTypeLeftClickDown
case "mouseup":
actionType = ActionTypeLeftClickUp
case "keydown":
actionType = ActionTypeKeyDown
case "keyup":
actionType = ActionTypeKeyUp
case "focus":
actionType = ActionTypeFocus
case "blur":
actionType = ActionTypeBlur
case "scroll":
actionType = ActionTypeScroll
case "dblclick":
actionType = ActionTypeDoubleClick
case "contextmenu":
actionType = ActionTypeRightClick
case "mouseover", "mouseout":
actionType = ActionTypeMouseOverAndOut
case "wheel":
actionType = ActionTypeMouseWheel
default:
actionType = ActionTypeUnknown
}
return &Action{
Type: actionType,
Element: listener.Element,
}
}
// HTMLElement represents a DOM element
type HTMLElement struct {
TagName string `json:"tagName,omitempty"`
ID string `json:"id,omitempty"`
Classes string `json:"classes,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
Hidden bool `json:"hidden,omitempty"`
OuterHTML string `json:"outerHTML,omitempty"`
Type string `json:"type,omitempty"`
Value string `json:"value,omitempty"`
CSSSelector string `json:"cssSelector,omitempty"`
XPath string `json:"xpath,omitempty"`
TextContent string `json:"textContent,omitempty"`
MD5Hash string `json:"md5Hash,omitempty"`
}
func (e *HTMLElement) String() string {
var builder strings.Builder
builder.WriteString(e.TagName)
if e.ID != "" {
fmt.Fprintf(&builder, "#%s", e.ID)
}
if e.Classes != "" {
fmt.Fprintf(&builder, ".%s", e.Classes)
}
if e.TextContent != "" {
trimmedContent := strings.Trim(e.TextContent, " \n\r\t")
fmt.Fprintf(&builder, " (%s)", trimmedContent)
}
value := builder.String()
return value
}
func (e *HTMLElement) Hash() string {
hasher := md5.New()
var parts []string
// Use stable identifiers
parts = append(parts, e.TagName)
if e.ID != "" {
parts = append(parts, "id:"+e.ID)
}
if e.Classes != "" {
parts = append(parts, "class:"+e.Classes)
}
// Add stable attributes
stableAttrs := getStableAttributes(e.Attributes)
for _, k := range stableAttrs {
parts = append(parts, fmt.Sprintf("%s:%s", k, e.Attributes[k]))
}
hashInput := strings.Join(parts, "|")
if IsDiagnosticEnabled {
fmt.Fprintf(os.Stderr, "[diagnostic] Element hash input: %s\n", hashInput)
}
hasher.Write([]byte(hashInput))
return hex.EncodeToString(hasher.Sum(nil))
}
// HTMLForm represents a form element
type HTMLForm struct {
TagName string `json:"tagName,omitempty"`
ID string `json:"id,omitempty"`
Classes string `json:"classes,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
Hidden bool `json:"hidden,omitempty"`
OuterHTML string `json:"outerHTML,omitempty"`
Action string `json:"action,omitempty"`
Method string `json:"method,omitempty"`
Elements []*HTMLElement `json:"elements,omitempty"`
CSSSelector string `json:"cssSelector,omitempty"`
XPath string `json:"xpath,omitempty"`
}
func (f *HTMLForm) Hash() string {
hasher := md5.New()
var parts []string
parts = append(parts, f.TagName)
if f.ID != "" {
parts = append(parts, "id:"+f.ID)
}
if f.Classes != "" {
parts = append(parts, "class:"+f.Classes)
}
// Add stable attributes
stableAttrs := getStableAttributes(f.Attributes)
for _, k := range stableAttrs {
parts = append(parts, fmt.Sprintf("%s:%s", k, f.Attributes[k]))
}
parts = append(parts, fmt.Sprintf("action:%s", f.Action), fmt.Sprintf("method:%s", f.Method))
// Include hashes of form elements
for _, element := range f.Elements {
parts = append(parts, element.Hash())
}
hashInput := strings.Join(parts, "|")
if IsDiagnosticEnabled {
fmt.Fprintf(os.Stderr, "[diagnostic] Form hash input: %s\n", hashInput)
}
hasher.Write([]byte(hashInput))
return hex.EncodeToString(hasher.Sum(nil))
}
// getStableAttributes returns a sorted slice of attribute keys that are considered stable.
func getStableAttributes(attrs map[string]string) []string {
stableKeys := map[string]struct{}{
"id": {},
"name": {},
"type": {},
"href": {},
"src": {},
"action": {},
"method": {},
"placeholder": {},
}
var stableAttrs []string
for key := range attrs {
if _, exists := stableKeys[key]; exists {
stableAttrs = append(stableAttrs, key)
}
}
// Sort the attributes to ensure consistent order for hashing.
sort.Strings(stableAttrs)
return stableAttrs
}
type EventListener struct {
Element *HTMLElement `json:"element,omitempty"`
Type string `json:"type,omitempty"`
Listener string `json:"listener,omitempty"`
}
// NavigationType represents the type of navigation
type NavigationType string
const (
// NavigationTypeForm represents a form navigation
NavigationTypeForm NavigationType = "form"
// NavigationTypeButton represents a button navigation
NavigationTypeButton NavigationType = "button"
// NavigationTypeLink represents a link navigation
NavigationTypeLink NavigationType = "link"
// NavigationTypeEventListener represents an event listener navigation
NavigationTypeEventListener NavigationType = "eventlistener"
)
================================================
FILE: pkg/engine/hybrid/crawl.go
================================================
package hybrid
import (
"bytes"
"errors"
"io"
"net/http"
"net/http/httputil"
"net/url"
"slices"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/engine/common"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/utils"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
mapsutil "github.com/projectdiscovery/utils/maps"
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
)
func (c *Crawler) navigateRequest(s *common.CrawlSession, request *navigation.Request) (*navigation.Response, error) {
depth := request.Depth + 1
response := &navigation.Response{
Depth: depth,
RootHostname: s.Hostname,
}
page, err := s.Browser.Page(proto.TargetCreateTarget{})
if err != nil {
return nil, errkit.Wrap(err, "hybrid: could not create target")
}
defer func() {
if err := page.Close(); err != nil {
gologger.Error().Msgf("Error closing page: %v\n", err)
}
}()
c.addHeadersToPage(page)
pageRouter := NewHijack(page)
pageRouter.SetPattern(&proto.FetchRequestPattern{
URLPattern: "*",
RequestStage: proto.FetchRequestStageResponse,
})
xhrRequests := []navigation.Request{}
go pageRouter.Start(func(e *proto.FetchRequestPaused) error {
URL, err := urlutil.Parse(e.Request.URL)
if err != nil {
return errkit.Wrap(err, "hybrid: could not parse URL")
}
body, _ := FetchGetResponseBody(page, e)
headers := make(map[string][]string)
for _, h := range e.ResponseHeaders {
headers[h.Name] = []string{h.Value}
}
var (
statusCode int
statucCodeText string
)
if e.ResponseStatusCode != nil {
statusCode = *e.ResponseStatusCode
}
if e.ResponseStatusText != "" {
statucCodeText = e.ResponseStatusText
} else {
statucCodeText = http.StatusText(statusCode)
}
httpreq, err := http.NewRequest(e.Request.Method, URL.String(), strings.NewReader(e.Request.PostData))
if err != nil {
return errkit.Wrap(err, "hybrid: could not new request")
}
// Note: headers are originally sent using `c.addHeadersToPage` below changes are done so that
// headers are reflected in request dump
// Headers, CustomHeaders, and Cookies are present in e.Request.Headers. We need to consider all of them and not only CustomHeaders
// Otherwise, we will miss headers and output will be inconsistent
if httpreq != nil {
for k, v := range e.Request.Headers {
httpreq.Header.Set(k, v.String())
}
}
httpresp := &http.Response{
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
StatusCode: statusCode,
Status: statucCodeText,
Header: headers,
Body: io.NopCloser(bytes.NewReader(body)),
Request: httpreq,
ContentLength: int64(len(body)),
}
var rawBytesRequest, rawBytesResponse []byte
if r, err := retryablehttp.FromRequest(httpreq); err == nil {
rawBytesRequest, _ = r.Dump()
} else {
rawBytesRequest, _ = httputil.DumpRequestOut(httpreq, true)
}
rawBytesResponse, _ = httputil.DumpResponse(httpresp, true)
bodyReader, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))
var technologies map[string]interface{}
if c.Options.Wappalyzer != nil {
fingerprints := c.Options.Wappalyzer.Fingerprint(headers, body)
technologies = make(map[string]interface{}, len(fingerprints))
for k := range fingerprints {
technologies[k] = struct{}{}
}
}
resp := &navigation.Response{
Resp: httpresp,
Body: string(body),
Reader: bodyReader,
Depth: depth,
RootHostname: s.Hostname,
Technologies: mapsutil.GetKeys(technologies),
StatusCode: statusCode,
Headers: utils.FlattenHeaders(headers),
Raw: string(rawBytesResponse),
ContentLength: httpresp.ContentLength,
KnowledgeBase: c.Options.ClassifyPage(string(body)),
}
response.ContentLength = resp.ContentLength
requestHeaders := make(map[string][]string)
for name, value := range e.Request.Headers {
requestHeaders[name] = []string{value.Str()}
}
shouldCapture := func(xhrExtraction bool) bool {
resourceTypes := []proto.NetworkResourceType{
proto.NetworkResourceTypeXHR,
proto.NetworkResourceTypeFetch,
proto.NetworkResourceTypeScript,
}
return xhrExtraction && slices.Contains(resourceTypes, e.ResourceType)
}
if shouldCapture(c.Options.Options.XhrExtraction) {
networkReq := navigation.Request{
URL: httpreq.URL.String(),
Method: httpreq.Method,
Body: e.Request.PostData,
}
if len(httpreq.Header) > 0 {
networkReq.Headers = utils.FlattenHeaders(httpreq.Header)
} else {
networkReq.Headers = utils.FlattenHeaders(requestHeaders)
}
xhrRequests = append(xhrRequests, networkReq)
}
// trim trailing /
normalizedheadlessURL := strings.TrimSuffix(e.Request.URL, "/")
matchOriginalURL := stringsutil.EqualFoldAny(request.URL, e.Request.URL, normalizedheadlessURL)
if matchOriginalURL {
request.Raw = string(rawBytesRequest)
response = resp
}
// process the raw response
navigationRequests := c.Options.Parser.ParseResponse(resp)
c.Enqueue(s.Queue, navigationRequests...)
// do not continue following the request if it's a redirect and redirects are disabled
if c.Options.Options.DisableRedirects && resp.IsRedirect() {
return nil
}
return FetchContinueRequest(page, e)
})() //nolint
defer func() {
if err := pageRouter.Stop(); err != nil {
gologger.Warning().Msgf("%s\n", err)
}
}()
timeout := time.Duration(c.Options.Options.Timeout) * time.Second
page = page.Timeout(timeout)
navigatedURLs := sliceutil.NewSyncSlice[string]()
navigatedURLs.Append(request.URL)
pageCtx, cancelPageEvents := page.WithCancel()
defer cancelPageEvents()
waitFrameEvents := pageCtx.EachEvent(func(e *proto.PageFrameNavigated) {
if e.Frame.ParentID == "" {
frameURL := e.Frame.URL
if frameURL != "" && frameURL != request.URL {
navigatedURLs.Append(frameURL)
}
}
})
go waitFrameEvents()
// wait the page to be fully loaded and becoming idle
waitNavigation := page.WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)
err = page.Navigate(request.URL)
if err != nil {
if c.Options.Options.DisableRedirects && response.IsRedirect() {
return response, nil
}
return nil, errkit.Wrap(err, "hybrid: could not navigate target")
}
waitNavigation()
// Wait the page to be stable a duration
timeStable := time.Duration(c.Options.Options.TimeStable) * time.Second
if timeout < timeStable {
gologger.Warning().Msgf("timeout is less than time stable, setting time stable to half of timeout to avoid timeout\n")
timeStable = timeout / 2
gologger.Warning().Msgf("setting time stable to %s\n", timeStable)
}
if err := page.WaitStable(timeStable); err != nil {
gologger.Warning().Msgf("could not wait for page to be stable: %s\n", err)
}
// simulate clicks on links with onclick handlers to discover JS redirects
time.Sleep(200 * time.Millisecond)
clickableLinks, err := page.Elements("a[onclick]")
if err == nil && len(clickableLinks) > 0 {
maxLinks := c.Options.Options.MaxOnclickLinks
linksToProcess := len(clickableLinks)
if linksToProcess > maxLinks {
linksToProcess = maxLinks
}
gologger.Debug().Msgf("Found %d clickable links with onclick handlers, processing %d", len(clickableLinks), linksToProcess)
for idx := 0; idx < linksToProcess; idx++ {
link := clickableLinks[idx]
beforeURL, err := page.Info()
if err != nil {
gologger.Error().Msgf("Could not get page info: %v", err)
continue
}
beforeURLStr := ""
if beforeURL != nil {
beforeURLStr = beforeURL.URL
}
// try to click the link using rod's Click method
clickErr := link.Click(proto.InputMouseButtonLeft, 1)
if clickErr != nil {
gologger.Debug().Msgf("Could not click link %d: %v", idx, clickErr)
continue
}
gologger.Debug().Msgf("Clicked onclick link [%d] at URL: %s", idx, beforeURLStr)
time.Sleep(1 * time.Second)
// check if URL changed (indicates redirect occurred)
currentURL, _ := page.Info()
if currentURL != nil && currentURL.URL != beforeURLStr {
gologger.Debug().Msgf("detected navigation to: %s", currentURL.URL)
navigatedURLs.Append(currentURL.URL)
if navErr := page.Navigate(request.URL); navErr != nil {
gologger.Warning().Msgf("Failed to navigate back to %s after onclick redirect: %v", request.URL, navErr)
if reloadErr := page.Reload(); reloadErr != nil {
gologger.Error().Msgf("Failed to reload page after navigation error: %v", reloadErr)
break
}
}
time.Sleep(500 * time.Millisecond)
}
}
}
var getDocumentDepth = int(-1)
getDocument := &proto.DOMGetDocument{Depth: &getDocumentDepth, Pierce: true}
result, err := getDocument.Call(page)
if err != nil {
return nil, errkit.Wrap(err, "hybrid: could not get dom")
}
var builder strings.Builder
traverseDOMNode(result.Root, &builder)
body, err := page.HTML()
if err != nil {
return nil, errkit.Wrap(err, "hybrid: could not get html")
}
parsed, err := urlutil.Parse(request.URL)
if err != nil {
return nil, errkit.Wrap(err, "hybrid: url could not be parsed")
}
if response == nil || response.Resp == nil {
// err is guaranteed to be nil, due to previous checks.
return nil, errors.New("hybrid: response is nil")
}
response.Resp.Request.URL = parsed.URL
// Create a copy of intrapolated shadow DOM elements and parse them separately
responseCopy := *response
responseCopy.Body = builder.String()
responseCopy.Reader, _ = goquery.NewDocumentFromReader(strings.NewReader(responseCopy.Body))
if responseCopy.Reader != nil {
navigationRequests := c.Options.Parser.ParseResponse(&responseCopy)
c.Enqueue(s.Queue, navigationRequests...)
}
response.Body = body
if response.Reader != nil {
response.Reader.Url, _ = url.Parse(request.URL)
if c.Options.Options.FormExtraction {
response.Forms = append(response.Forms, utils.ParseFormFields(response.Reader)...)
}
}
response.Reader, err = goquery.NewDocumentFromReader(strings.NewReader(response.Body))
if err != nil {
return nil, errkit.Wrap(err, "hybrid: could not parse html")
}
response.XhrRequests = xhrRequests
// enqueue JS-triggered navigation URLs that were detected
navigatedURLs.Each(func(i int, navURL string) error {
if navURL != request.URL {
parsed, err := urlutil.Parse(navURL)
if err == nil {
navReq := &navigation.Request{
URL: parsed.String(),
Depth: depth,
RootHostname: s.Hostname,
}
c.Enqueue(s.Queue, navReq)
gologger.Debug().Msgf("enqueued JS navigation: %s", navURL)
}
}
return nil
})
return response, nil
}
func (c *Crawler) addHeadersToPage(page *rod.Page) {
if len(c.Headers) == 0 {
return
}
var arr []string
for k, v := range c.Headers {
switch {
case stringsutil.EqualFoldAny(k, "User-Agent"):
userAgentParams := &proto.NetworkSetUserAgentOverride{
UserAgent: v,
}
if err := page.SetUserAgent(userAgentParams); err != nil {
gologger.Error().Msgf("headless: could not set user agent: %v", err)
}
default:
arr = append(arr, k, v)
}
}
if len(arr) > 0 {
_, err := page.SetExtraHeaders(arr)
if err != nil {
gologger.Error().Msgf("headless: could not set extra headers: %v", err)
}
}
}
// traverseDOMNode performs traversal of node completely building a pseudo-HTML
// from it including the Shadow DOM, Pseudo elements and other children.
//
// TODO: Remove this method when we implement human-like browser navigation
// which will anyway use browser APIs to find elements instead of goquery
// where they will have shadow DOM information.
func traverseDOMNode(node *proto.DOMNode, builder *strings.Builder) {
buildDOMFromNode(node, builder)
if node.TemplateContent != nil {
traverseDOMNode(node.TemplateContent, builder)
}
if node.ContentDocument != nil {
traverseDOMNode(node.ContentDocument, builder)
}
for _, children := range node.Children {
traverseDOMNode(children, builder)
}
for _, shadow := range node.ShadowRoots {
traverseDOMNode(shadow, builder)
}
for _, pseudo := range node.PseudoElements {
traverseDOMNode(pseudo, builder)
}
}
const (
elementNode = 1
)
var knownElements = map[string]struct{}{
"a": {}, "applet": {}, "area": {}, "audio": {}, "base": {}, "blockquote": {}, "body": {}, "button": {}, "embed": {}, "form": {}, "frame": {}, "html": {}, "iframe": {}, "img": {}, "import": {}, "input": {}, "isindex": {}, "link": {}, "meta": {}, "object": {}, "script": {}, "svg": {}, "table": {}, "video": {},
}
func buildDOMFromNode(node *proto.DOMNode, builder *strings.Builder) {
if node.NodeType != elementNode {
return
}
if _, ok := knownElements[node.LocalName]; !ok {
return
}
builder.WriteRune('<')
builder.WriteString(node.LocalName)
builder.WriteRune(' ')
if len(node.Attributes) > 0 {
for i := 0; i < len(node.Attributes); i = i + 2 {
builder.WriteString(node.Attributes[i])
builder.WriteRune('=')
builder.WriteString("\"")
builder.WriteString(node.Attributes[i+1])
builder.WriteString("\"")
builder.WriteRune(' ')
}
}
builder.WriteRune('>')
builder.WriteString("")
builder.WriteString(node.LocalName)
builder.WriteRune('>')
}
================================================
FILE: pkg/engine/hybrid/doc.go
================================================
// Package hybrid implements the functionality for a hybrid-headless crawler.
// It uses both headless browser and net/http for making requests, and goquery for processing rawand dom-rendered web page HTML.
package hybrid
================================================
FILE: pkg/engine/hybrid/hijack.go
================================================
package hybrid
import (
"encoding/base64"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/proto"
)
// NewHijack create hijack from page.
func NewHijack(page *rod.Page) *Hijack {
return &Hijack{
page: page,
disable: &proto.FetchDisable{},
}
}
// HijackHandler type
type HijackHandler = func(e *proto.FetchRequestPaused) error
// Hijack is a hijack handler
type Hijack struct {
page *rod.Page
enable *proto.FetchEnable
disable *proto.FetchDisable
cancel func()
}
// SetPattern set pattern directly
func (h *Hijack) SetPattern(pattern *proto.FetchRequestPattern) {
h.enable = &proto.FetchEnable{
Patterns: []*proto.FetchRequestPattern{pattern},
}
}
// Start hijack.
func (h *Hijack) Start(handler HijackHandler) func() error {
if h.enable == nil {
panic("hijack pattern not set")
}
p, cancel := h.page.WithCancel()
h.cancel = cancel
err := h.enable.Call(p)
if err != nil {
return func() error { return err }
}
wait := p.EachEvent(func(e *proto.FetchRequestPaused) {
if handler != nil {
err = handler(e)
}
})
return func() error {
wait()
return err
}
}
// Stop
func (h *Hijack) Stop() error {
if h.cancel != nil {
h.cancel()
}
return h.disable.Call(h.page)
}
// FetchGetResponseBody get request body.
func FetchGetResponseBody(page *rod.Page, e *proto.FetchRequestPaused) ([]byte, error) {
m := proto.FetchGetResponseBody{
RequestID: e.RequestID,
}
r, err := m.Call(page)
if err != nil {
return nil, err
}
if !r.Base64Encoded {
return []byte(r.Body), nil
}
bs, err := base64.StdEncoding.DecodeString(r.Body)
if err != nil {
return nil, err
}
return bs, nil
}
// FetchContinueRequest continue request
func FetchContinueRequest(page *rod.Page, e *proto.FetchRequestPaused) error {
m := proto.FetchContinueRequest{
RequestID: e.RequestID,
}
return m.Call(page)
}
================================================
FILE: pkg/engine/hybrid/hybrid.go
================================================
package hybrid
import (
"fmt"
"os"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher"
"github.com/go-rod/rod/lib/launcher/flags"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/engine/common"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/types"
"github.com/projectdiscovery/katana/pkg/utils"
"github.com/projectdiscovery/utils/errkit"
urlutil "github.com/projectdiscovery/utils/url"
)
// Crawler is a standard crawler instance
type Crawler struct {
*common.Shared
browser *rod.Browser
// TODO: Remove the Chrome PID kill code in favor of using Leakless(true).
// This change will be made if there are no complaints about zombie Chrome processes.
// References:
// https://github.com/projectdiscovery/katana/issues/632
// https://github.com/projectdiscovery/httpx/issues/1425
// previousPIDs map[int32]struct{} // track already running PIDs
tempDir string
}
// New returns a new standard crawler instance
func New(options *types.CrawlerOptions) (*Crawler, error) {
var dataStore string
var err error
if options.Options.ChromeDataDir != "" {
dataStore = options.Options.ChromeDataDir
} else {
dataStore, err = os.MkdirTemp("", "katana-*")
if err != nil {
return nil, errkit.Wrap(err, "hybrid: could not create temporary directory")
}
}
// previousPIDs := processutil.FindProcesses(processutil.IsChromeProcess)
var launcherURL string
var chromeLauncher *launcher.Launcher
if options.Options.ChromeWSUrl != "" {
launcherURL = options.Options.ChromeWSUrl
} else {
// create new chrome launcher instance
chromeLauncher, err = buildChromeLauncher(options, dataStore)
if err != nil {
return nil, err
}
// launch chrome headless process
launcherURL, err = chromeLauncher.Launch()
if err != nil {
return nil, err
}
}
browser := rod.New().ControlURL(launcherURL)
if browserErr := browser.Connect(); browserErr != nil {
return nil, errkit.Wrap(browserErr, fmt.Sprintf("hybrid: failed to connect to chrome instance at %s", launcherURL))
}
// create a new browser instance (default to incognito mode)
if !options.Options.HeadlessNoIncognito {
incognito, err := browser.Incognito()
if err != nil {
if chromeLauncher != nil {
chromeLauncher.Kill()
}
return nil, errkit.Wrap(err, "hybrid: failed to create incognito browser")
}
browser = incognito
}
shared, err := common.NewShared(options)
if err != nil {
return nil, errkit.Wrap(err, "hybrid")
}
crawler := &Crawler{
Shared: shared,
browser: browser,
// previousPIDs: previousPIDs,
tempDir: dataStore,
}
return crawler, nil
}
// Close closes the crawler process
func (c *Crawler) Close() error {
if c.Options.Options.ChromeDataDir == "" {
if err := os.RemoveAll(c.tempDir); err != nil {
return err
}
}
// processutil.CloseProcesses(processutil.IsChromeProcess, c.previousPIDs)
return nil
}
// Crawl crawls a URL with the specified options
func (c *Crawler) Crawl(rootURL string) error {
crawlSession, err := c.NewCrawlSessionWithURL(rootURL)
if err != nil {
return errkit.Wrap(err, "hybrid")
}
crawlSession.Browser = c.browser
defer crawlSession.CancelFunc()
gologger.Info().Msgf("Started headless crawling for => %v", rootURL)
if err := c.Do(crawlSession, c.navigateRequest); err != nil {
return errkit.Wrap(err, "hybrid")
}
return nil
}
// Do executes the crawling loop with browser-safe concurrency.
// Unlike the base implementation, this uses sequential processing (concurrency=1)
// because Chrome DevTools Protocol operations cannot safely run concurrently
// on the same browser instance. Multiple concurrent page operations cause
// race conditions, navigation conflicts, and network interception issues.
func (c *Crawler) Do(crawlSession *common.CrawlSession, doRequest common.DoRequestFunc) error {
for item := range crawlSession.Queue.Pop() {
if ctxErr := crawlSession.Ctx.Err(); ctxErr != nil {
return ctxErr
}
req, ok := item.(*navigation.Request)
if !ok {
continue
}
if !utils.IsURL(req.URL) {
if c.Options.Options.OnSkipURL != nil {
c.Options.Options.OnSkipURL(req.URL)
}
gologger.Debug().Msgf("`%v` not a url. skipping", req.URL)
continue
}
if !c.Options.ValidatePath(req.URL) {
gologger.Debug().Msgf("`%v` filtered path. skipping", req.URL)
continue
}
inScope, scopeErr := c.Options.ValidateScope(req.URL, crawlSession.Hostname)
if scopeErr != nil {
gologger.Debug().Msgf("Error validating scope for `%v`: %v. skipping", req.URL, scopeErr)
continue
}
if !req.SkipValidation && !inScope {
gologger.Debug().Msgf("`%v` not in scope. skipping", req.URL)
continue
}
c.Options.RateLimit.Take()
if c.Options.Options.Delay > 0 {
time.Sleep(time.Duration(c.Options.Options.Delay) * time.Second)
}
resp, err := doRequest(crawlSession, req)
if inScope {
c.Output(req, resp, err)
}
if err != nil {
gologger.Warning().Msgf("Could not request seed URL %s: %s\n", req.URL, err)
outputError := &output.Error{
Timestamp: time.Now(),
Endpoint: req.RequestURL(),
Source: req.Source,
Error: err.Error(),
}
_ = c.Options.OutputWriter.WriteErr(outputError)
continue
}
if resp == nil || resp.Resp == nil || resp.Reader == nil {
continue
}
if c.Options.Options.DisableRedirects && resp.IsRedirect() {
continue
}
navigationRequests := c.Options.Parser.ParseResponse(resp)
c.Enqueue(crawlSession.Queue, navigationRequests...)
}
return nil
}
// buildChromeLauncher builds a new chrome launcher instance
func buildChromeLauncher(options *types.CrawlerOptions, dataStore string) (*launcher.Launcher, error) {
chromeLauncher := launcher.New().
Leakless(true).
Set("disable-gpu", "true").
Set("ignore-certificate-errors", "true").
Set("ignore-certificate-errors", "1").
Set("disable-crash-reporter", "true").
Set("disable-notifications", "true").
Set("hide-scrollbars", "true").
Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).
Set("mute-audio", "true").
Delete("use-mock-keychain").
UserDataDir(dataStore)
if options.Options.UseInstalledChrome {
if options.Options.SystemChromePath != "" {
chromeLauncher.Bin(options.Options.SystemChromePath)
} else {
if chromePath, hasChrome := launcher.LookPath(); hasChrome {
chromeLauncher.Bin(chromePath)
} else {
return nil, errkit.New("hybrid: the chrome browser is not installed")
}
}
}
if options.Options.SystemChromePath != "" {
chromeLauncher.Bin(options.Options.SystemChromePath)
}
if options.Options.ShowBrowser {
chromeLauncher = chromeLauncher.Headless(false)
} else {
chromeLauncher = chromeLauncher.Headless(true)
}
if options.Options.HeadlessNoSandbox {
chromeLauncher.Set("no-sandbox", "true")
}
if options.Options.Proxy != "" && options.Options.Headless {
proxyURL, err := urlutil.Parse(options.Options.Proxy)
if err != nil {
return nil, err
}
chromeLauncher.Set("proxy-server", proxyURL.String())
}
for k, v := range options.Options.ParseHeadlessOptionalArguments() {
chromeLauncher.Set(flags.Flag(k), v)
}
return chromeLauncher, nil
}
================================================
FILE: pkg/engine/parser/files/request.go
================================================
package files
import (
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/retryablehttp-go"
)
type visitFunc func(URL string) ([]*navigation.Request, error)
type KnownFiles struct {
parsers []visitFunc
httpclient *retryablehttp.Client
}
// New returns a new known files parser instance
func New(httpclient *retryablehttp.Client, files string) *KnownFiles {
parser := &KnownFiles{
httpclient: httpclient,
}
switch files {
case "robotstxt":
crawler := &robotsTxtCrawler{httpclient: httpclient}
parser.parsers = append(parser.parsers, crawler.Visit)
case "sitemapxml":
crawler := &sitemapXmlCrawler{httpclient: httpclient}
parser.parsers = append(parser.parsers, crawler.Visit)
default:
crawler := &robotsTxtCrawler{httpclient: httpclient}
parser.parsers = append(parser.parsers, crawler.Visit)
another := &sitemapXmlCrawler{httpclient: httpclient}
parser.parsers = append(parser.parsers, another.Visit)
}
return parser
}
// Request requests all known files with visitors
func (k *KnownFiles) Request(URL string) (navigationRequests []*navigation.Request, err error) {
for _, visitor := range k.parsers {
navRequests, err := visitor(URL)
if err != nil {
return navigationRequests, err
}
navigationRequests = append(navigationRequests, navRequests...)
}
return
}
================================================
FILE: pkg/engine/parser/files/robotstxt.go
================================================
package files
import (
"bufio"
"fmt"
"io"
"net/http"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/utils"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
)
type robotsTxtCrawler struct {
httpclient *retryablehttp.Client
}
// Visit visits the provided URL with file crawlers
func (r *robotsTxtCrawler) Visit(URL string) ([]*navigation.Request, error) {
URL = strings.TrimSuffix(URL, "/")
requestURL := fmt.Sprintf("%s/robots.txt", URL)
req, err := retryablehttp.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
return nil, errkit.Wrap(err, "robotscrawler: could not create request")
}
req.Header.Set("User-Agent", utils.WebUserAgent())
resp, err := r.httpclient.Do(req)
if err != nil {
return nil, errkit.Wrap(err, "robotscrawler: could not do request")
}
defer func() {
if err := resp.Body.Close(); err != nil {
gologger.Error().Msgf("Error closing response body: %v\n", err)
}
}()
return r.parseReader(resp.Body, resp)
}
func (r *robotsTxtCrawler) parseReader(reader io.Reader, resp *http.Response) (navigationRequests []*navigation.Request, err error) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
text := scanner.Text()
splitted := strings.SplitN(text, ": ", 2)
if len(splitted) < 2 {
continue
}
directive := strings.ToLower(splitted[0])
if strings.HasPrefix(directive, "allow") || strings.EqualFold(directive, "disallow") {
navResp := &navigation.Response{
Depth: 2,
Resp: resp,
StatusCode: resp.StatusCode,
Headers: utils.FlattenHeaders(resp.Header),
}
navRequest := navigation.NewNavigationRequestURLFromResponse(strings.Trim(splitted[1], " "), resp.Request.URL.String(), "file", "robotstxt", navResp)
navigationRequests = append(navigationRequests, navRequest)
}
}
return
}
================================================
FILE: pkg/engine/parser/files/robotstxt_test.go
================================================
package files
import (
"net/http"
"strings"
"testing"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/stretchr/testify/require"
)
func TestRobotsTxtParseReader(t *testing.T) {
requests := []string{}
crawler := &robotsTxtCrawler{}
content := `User-agent: *
Disallow: /test/misc/known-files/robots.txt.found
User-agent: *
Disallow: /test/includes/
# User-agent: Googlebot
# Allow: /random/
Sitemap: https://example.com/sitemap.xml`
parsed, err := urlutil.Parse("http://localhost/robots.txt")
require.Nil(t, err)
navigationRequests, err := crawler.parseReader(strings.NewReader(content), &http.Response{Request: &http.Request{URL: parsed.URL}})
require.Nil(t, err)
for _, navReq := range navigationRequests {
requests = append(requests, navReq.URL)
}
require.ElementsMatch(t, requests, []string{
"http://localhost/test/includes/",
"http://localhost/test/misc/known-files/robots.txt.found",
}, "could not get correct elements")
}
================================================
FILE: pkg/engine/parser/files/sitemapxml.go
================================================
package files
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/utils"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
)
type sitemapXmlCrawler struct {
httpclient *retryablehttp.Client
}
// Visit visits the provided URL with file crawlers
func (r *sitemapXmlCrawler) Visit(URL string) (navigationRequests []*navigation.Request, err error) {
URL = strings.TrimSuffix(URL, "/")
requestURL := fmt.Sprintf("%s/sitemap.xml", URL)
req, err := retryablehttp.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
return nil, errkit.Wrap(err, "sitemapcrawler: could not create request")
}
req.Header.Set("User-Agent", utils.WebUserAgent())
resp, err := r.httpclient.Do(req)
if err != nil {
return nil, errkit.Wrap(err, "sitemapcrawler: could not do request")
}
defer func() {
if err := resp.Body.Close(); err != nil {
gologger.Error().Msgf("Error closing response body: %v\n", err)
}
}()
navigationRequests, err = r.parseReader(resp.Body, resp)
if err != nil {
return nil, errkit.Wrap(err, "sitemapcrawler: could not parse sitemap")
}
return
}
type sitemapStruct struct {
URLs []parsedURL `xml:"url"`
Sitemap []parsedURL `xml:"sitemap"`
}
type parsedURL struct {
Loc string `xml:"loc"`
}
func (r *sitemapXmlCrawler) parseReader(reader io.Reader, resp *http.Response) (navigationRequests []*navigation.Request, err error) {
sitemap := sitemapStruct{}
if err := xml.NewDecoder(reader).Decode(&sitemap); err != nil {
return nil, errkit.Wrap(err, "sitemapcrawler: could not decode xml")
}
for _, url := range sitemap.URLs {
navResp := &navigation.Response{
Depth: 2,
Resp: resp,
StatusCode: resp.StatusCode,
Headers: utils.FlattenHeaders(resp.Header),
}
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(strings.Trim(url.Loc, " \t\n"), resp.Request.URL.String(), "file", "sitemapxml", navResp))
}
for _, url := range sitemap.Sitemap {
navResp := &navigation.Response{
Depth: 2,
Resp: resp,
StatusCode: resp.StatusCode,
Headers: utils.FlattenHeaders(resp.Header),
}
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(strings.Trim(url.Loc, " \t\n"), resp.Request.URL.String(), "file", "sitemapxml", navResp))
}
return
}
================================================
FILE: pkg/engine/parser/files/sitemapxml_test.go
================================================
package files
import (
"net/http"
"strings"
"testing"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/stretchr/testify/require"
)
func TestSitemapXmlParseReader(t *testing.T) {
requests := []string{}
crawler := &sitemapXmlCrawler{}
content := `
http://security-crawl-maze.app/test/misc/known-files/sitemap.xml.found
2019-06-19T12:00:00+00:00
`
parsed, err := urlutil.Parse("http://security-crawl-maze.app/sitemap.xml")
require.Nil(t, err)
navigationRequests, err := crawler.parseReader(strings.NewReader(content), &http.Response{Request: &http.Request{URL: parsed.URL}})
require.Nil(t, err)
for _, navReq := range navigationRequests {
requests = append(requests, navReq.URL)
}
require.ElementsMatch(t, requests, []string{
"http://security-crawl-maze.app/test/misc/known-files/sitemap.xml.found",
}, "could not get correct elements")
}
================================================
FILE: pkg/engine/parser/parser.go
================================================
package parser
import (
"mime/multipart"
"net/http"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/utils"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
"golang.org/x/net/html"
)
// responseParserFunc is a function that parses the document returning
// new navigation items or requests for the crawler.
type ResponseParserFunc func(resp *navigation.Response) []*navigation.Request
type Parser []responseParser
type responseParserType int
const (
headerParser responseParserType = iota + 1
bodyParser
contentParser
)
type responseParser struct {
parserType responseParserType
parserFunc ResponseParserFunc
}
func NewResponseParser() *Parser {
return &Parser{
// Header based parsers
{headerParser, headerContentLocationParser},
{headerParser, headerLinkParser},
{headerParser, headerRefreshParser},
// Body based parsers
{bodyParser, bodyATagParser},
{bodyParser, bodyLinkHrefTagParser},
{bodyParser, bodyBackgroundTagParser},
{bodyParser, bodyAudioTagParser},
{bodyParser, bodyAppletTagParser},
{bodyParser, bodyImgTagParser},
{bodyParser, bodyObjectTagParser},
{bodyParser, bodySvgTagParser},
{bodyParser, bodyTableTagParser},
{bodyParser, bodyVideoTagParser},
{bodyParser, bodyButtonFormactionTagParser},
{bodyParser, bodyBlockquoteCiteTagParser},
{bodyParser, bodyFrameSrcTagParser},
{bodyParser, bodyMapAreaPingTagParser},
{bodyParser, bodyBaseHrefTagParser},
{bodyParser, bodyImportImplementationTagParser},
{bodyParser, bodyEmbedTagParser},
{bodyParser, bodyFrameTagParser},
{bodyParser, bodyIframeTagParser},
{bodyParser, bodyInputSrcTagParser},
{bodyParser, bodyIsindexActionTagParser},
{bodyParser, bodyScriptSrcTagParser},
{bodyParser, bodyMetaContentTagParser},
{bodyParser, bodyHtmlManifestTagParser},
{bodyParser, bodyHtmlDoctypeTagParser},
{bodyParser, bodyHtmxAttrParser},
// custom field regex parser
{bodyParser, customFieldRegexParser},
}
}
// parseResponse runs the response parsers on the navigation response
func (p *Parser) ParseResponse(resp *navigation.Response) (navigationRequests []*navigation.Request) {
for _, parser := range *p {
switch {
case parser.parserType == headerParser && resp.Resp != nil:
navigationRequests = appendFiltered(navigationRequests, parser.parserFunc(resp))
case parser.parserType == bodyParser && resp.Reader != nil:
navigationRequests = appendFiltered(navigationRequests, parser.parserFunc(resp))
case parser.parserType == contentParser && len(resp.Body) > 0:
navigationRequests = appendFiltered(navigationRequests, parser.parserFunc(resp))
}
}
return
}
// appendFiltered filters navigation requests and appends valid ones to the slice
func appendFiltered(existing []*navigation.Request, new []*navigation.Request) []*navigation.Request {
for _, req := range new {
if isValidNavigationRequest(req) {
existing = append(existing, req)
}
}
return existing
}
func isValidNavigationRequest(req *navigation.Request) bool {
if req == nil {
return false
}
url := strings.TrimSpace(req.URL)
if url == "" {
return false
}
lc := strings.ToLower(url)
return !strings.HasPrefix(lc, "data:") &&
!strings.HasPrefix(lc, "mailto:") &&
!strings.HasPrefix(lc, "javascript:") &&
!strings.HasPrefix(lc, "vbscript:")
}
// -------------------------------------------------------------------------
// Begin Header based parsers
// -------------------------------------------------------------------------
// headerContentLocationParser parsers Content-Location header from response
func headerContentLocationParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
header := resp.Resp.Header.Get("Content-Location")
if header == "" {
return
}
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(header, resp.Resp.Request.URL.String(), "header", "content-location", resp))
return
}
// headerLinkParser parsers Link header from response
func headerLinkParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
header := resp.Resp.Header.Get("Link")
if header == "" {
return
}
values := utils.ParseLinkTag(header)
for _, value := range values {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(value, resp.Resp.Request.URL.String(), "header", "link", resp))
}
return
}
// headerLocationParser parsers Location header from response
func headerLocationParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
header := resp.Resp.Header.Get("Location")
if header == "" {
return
}
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(header, resp.Resp.Request.URL.String(), "header", "location", resp))
return
}
// headerRefreshParser parsers Refresh header from response
func headerRefreshParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
header := resp.Resp.Header.Get("Refresh")
if header == "" {
return
}
values := utils.ParseRefreshTag(header)
if values == "" {
return
}
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(values, resp.Resp.Request.URL.String(), "header", "refresh", resp))
return
}
// -------------------------------------------------------------------------
// Begin Body based parsers
// -------------------------------------------------------------------------
// bodyATagParser parses A tag from response
func bodyATagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("a").Each(func(i int, item *goquery.Selection) {
href, ok := item.Attr("href")
if ok && href != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(href, resp.Resp.Request.URL.String(), "a", "href", resp))
}
ping, ok := item.Attr("ping")
if ok && ping != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(ping, resp.Resp.Request.URL.String(), "a", "ping", resp))
}
})
return
}
// bodyLinkHrefTagParser parses link tag from response
func bodyLinkHrefTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("link[href]").Each(func(i int, item *goquery.Selection) {
href, ok := item.Attr("href")
if ok && href != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(href, resp.Resp.Request.URL.String(), "link", "href", resp))
}
})
return
}
// bodyEmbedTagParser parses Embed tag from response
func bodyEmbedTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("embed[src]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "embed", "src", resp))
}
})
return
}
// bodyFrameTagParser parses frame tag from response
func bodyFrameTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("frame[src]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "frame", "src", resp))
}
})
return
}
// bodyIframeTagParser parses iframe tag from response
func bodyIframeTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("iframe").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "iframe", "src", resp))
}
srcDoc, ok := item.Attr("srcdoc")
if ok && srcDoc != "" {
endpoints := utils.ExtractRelativeEndpoints(srcDoc)
for _, item := range endpoints {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), "iframe", "srcdoc", resp))
}
}
})
return
}
// bodyInputSrcTagParser parses input image src tag from response
func bodyInputSrcTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("input[type='image' i]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "input-image", "src", resp))
}
})
return
}
// bodyIsindexActionTagParser parses isindex action tag from response
func bodyIsindexActionTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("isindex[action]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("action")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "isindex", "action", resp))
}
})
return
}
// bodyScriptSrcTagParser parses script src tag from response
func bodyScriptSrcTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("script[src]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "script", "src", resp))
}
})
return
}
// bodyBackgroundTagParser parses body background tag from response
func bodyBackgroundTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("body[background]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("background")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "body", "background", resp))
}
})
return
}
// bodyAudioTagParser parses body audio tag from response
func bodyAudioTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("audio").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "audio", "src", resp))
}
item.Find("source").Each(func(i int, s *goquery.Selection) {
src, ok := s.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "audio", "source", resp))
}
srcSet, ok := s.Attr("srcset")
if ok && srcSet != "" {
for _, value := range utils.ParseSRCSetTag(srcSet) {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(value, resp.Resp.Request.URL.String(), "audio", "sourcesrcset", resp))
}
}
})
})
return
}
// bodyAppletTagParser parses body applet tag from response
func bodyAppletTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("applet").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("archive")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "applet", "archive", resp))
}
srcCodebase, ok := item.Attr("codebase")
if ok && srcCodebase != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcCodebase, resp.Resp.Request.URL.String(), "applet", "codebase", resp))
}
})
return
}
// bodyImgTagParser parses Img tag from response
func bodyImgTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("img").Each(func(i int, item *goquery.Selection) {
srcDynsrc, ok := item.Attr("dynsrc")
if ok && srcDynsrc != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcDynsrc, resp.Resp.Request.URL.String(), "img", "dynsrc", resp))
}
srcLongdesc, ok := item.Attr("longdesc")
if ok && srcLongdesc != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcLongdesc, resp.Resp.Request.URL.String(), "img", "longdesc", resp))
}
srcLowsrc, ok := item.Attr("lowsrc")
if ok && srcLowsrc != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcLowsrc, resp.Resp.Request.URL.String(), "img", "lowsrc", resp))
}
src, ok := item.Attr("src")
if ok && src != "" && src != "#" {
if strings.HasPrefix(src, "data:") {
// TODO: Add data:uri/data:image parsing
return
}
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "img", "src", resp))
}
srcSet, ok := item.Attr("srcset")
if ok && srcSet != "" {
for _, value := range utils.ParseSRCSetTag(srcSet) {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(value, resp.Resp.Request.URL.String(), "img", "srcset", resp))
}
}
})
return
}
// bodyObjectTagParser parses object tag from response
func bodyObjectTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("object").Each(func(i int, item *goquery.Selection) {
srcData, ok := item.Attr("data")
if ok && srcData != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcData, resp.Resp.Request.URL.String(), "src", "data", resp))
}
srcCodebase, ok := item.Attr("codebase")
if ok && srcCodebase != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcCodebase, resp.Resp.Request.URL.String(), "src", "codebase", resp))
}
item.Find("param").Each(func(i int, s *goquery.Selection) {
srcValue, ok := s.Attr("value")
if ok && srcValue != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcValue, resp.Resp.Request.URL.String(), "src", "value", resp))
}
})
})
return
}
// bodySvgTagParser parses svg tag from response
func bodySvgTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("svg").Each(func(i int, item *goquery.Selection) {
item.Find("image").Each(func(i int, s *goquery.Selection) {
hrefData, ok := s.Attr("href")
if ok && hrefData != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(hrefData, resp.Resp.Request.URL.String(), "svg", "image-href", resp))
}
})
item.Find("script").Each(func(i int, s *goquery.Selection) {
hrefData, ok := s.Attr("href")
if ok && hrefData != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(hrefData, resp.Resp.Request.URL.String(), "svg", "script-href", resp))
}
})
})
return
}
// bodyTableTagParser parses table tag from response
func bodyTableTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("table").Each(func(i int, item *goquery.Selection) {
srcData, ok := item.Attr("background")
if ok && srcData != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcData, resp.Resp.Request.URL.String(), "table", "background", resp))
}
item.Find("td").Each(func(i int, s *goquery.Selection) {
srcValue, ok := s.Attr("background")
if ok && srcValue != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcValue, resp.Resp.Request.URL.String(), "table", "td-background", resp))
}
})
})
return
}
// bodyVideoTagParser parses video tag from response
func bodyVideoTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("video").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "video", "src", resp))
}
srcData, ok := item.Attr("poster")
if ok && srcData != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcData, resp.Resp.Request.URL.String(), "video", "poster", resp))
}
item.Find("track").Each(func(i int, s *goquery.Selection) {
srcValue, ok := s.Attr("src")
if ok && srcValue != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcValue, resp.Resp.Request.URL.String(), "video", "track-src", resp))
}
})
})
return
}
// bodyBlockquoteCiteTagParser parses blockquote cite tag from response
func bodyBlockquoteCiteTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("blockquote[cite]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("cite")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "blockquote", "cite", resp))
}
})
return
}
// bodyFrameSrcTagParser parses frame src tag from response
func bodyFrameSrcTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("frame[src]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("src")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "frame", "src", resp))
}
})
return
}
// bodyMapAreaPingTagParser parses map area ping tag from response
func bodyMapAreaPingTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("area[ping]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("ping")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "area", "ping", resp))
}
})
return
}
// bodyBaseHrefTagParser parses base href tag from response
func bodyBaseHrefTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("base[href]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("href")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "base", "href", resp))
}
})
return
}
// bodyImportImplementationTagParser parses import implementation tag from response
func bodyImportImplementationTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("import[implementation]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("implementation")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "import", "implementation", resp))
}
})
return
}
// bodyButtonFormactionTagParser parses button formaction tag from response
func bodyButtonFormactionTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("button[formaction]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("formaction")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "button", "formaction", resp))
}
})
return
}
// bodyHtmlManifestTagParser parses body manifest tag from response
func bodyHtmlManifestTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("html[manifest]").Each(func(i int, item *goquery.Selection) {
src, ok := item.Attr("manifest")
if ok && src != "" {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), "html", "manifest", resp))
}
})
return
}
// bodyHtmlDoctypeTagParser parses body doctype tag from response
func bodyHtmlDoctypeTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
if len(resp.Reader.Nodes) < 1 || resp.Reader.Nodes[0].FirstChild == nil {
return
}
docTypeNode := resp.Reader.Nodes[0].FirstChild
if docTypeNode.Type != html.DoctypeNode {
return
}
if len(docTypeNode.Attr) == 0 || strings.ToLower(docTypeNode.Attr[0].Key) != "system" {
return
}
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(docTypeNode.Attr[0].Val, resp.Resp.Request.URL.String(), "html", "doctype", resp))
return
}
// bodyFormTagParser parses forms from response
func bodyFormTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("form").Each(func(i int, item *goquery.Selection) {
href, _ := item.Attr("action")
encType, ok := item.Attr("enctype")
if !ok || encType == "" {
encType = "application/x-www-form-urlencoded"
}
method, _ := item.Attr("method")
if method == "" {
method = "GET"
}
method = strings.ToUpper(method)
actionURL := resp.AbsoluteURL(href)
if actionURL == "" {
return
}
parsed, err := urlutil.Parse(actionURL)
if err != nil {
gologger.Warning().Msgf("bodyFormTagParser :failed to parse url %v got %v", actionURL, err)
return
}
isMultipartForm := strings.HasPrefix(encType, "multipart/")
queryValuesWriter := urlutil.NewOrderedParams()
queryValuesWriter.IncludeEquals = true
var sb strings.Builder
var multipartWriter *multipart.Writer
if isMultipartForm {
multipartWriter = multipart.NewWriter(&sb)
}
// Get the form field suggestions for all elements in the form
formFields := []interface{}{}
item.Find("input, select, textarea").Each(func(index int, item *goquery.Selection) {
if len(item.Nodes) == 0 {
return
}
formFields = append(formFields, utils.ConvertGoquerySelectionToFormField(item))
})
dataMap := utils.FormFillSuggestions(formFields)
dataMap.Iterate(func(key, value string) bool {
if key == "" {
return true
}
if isMultipartForm {
_ = multipartWriter.WriteField(key, value)
} else {
queryValuesWriter.Set(key, value)
}
return true
})
// Guess content-type
var contentType string
if multipartWriter != nil {
_ = multipartWriter.Close()
contentType = multipartWriter.FormDataContentType()
} else {
contentType = encType
}
req := &navigation.Request{
Method: method,
URL: actionURL,
Depth: resp.Depth,
RootHostname: resp.RootHostname,
Tag: "form",
Attribute: "action",
Source: resp.Resp.Request.URL.String(),
}
switch method {
case "GET":
parsed.Params.Merge(queryValuesWriter.Encode())
req.URL = parsed.String()
case "POST":
if multipartWriter != nil {
req.Body = sb.String()
} else {
req.Body = queryValuesWriter.Encode()
}
req.Headers = make(map[string]string)
req.Headers["Content-Type"] = contentType
}
navigationRequests = append(navigationRequests, req)
})
return
}
// bodyMetaContentTagParser parses meta content tag from response
func bodyMetaContentTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("meta").Each(func(i int, item *goquery.Selection) {
header, ok := item.Attr("content")
if !ok {
return
}
extracted := utils.ExtractRelativeEndpoints(header)
for _, item := range extracted {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), "meta", "refresh", resp))
}
})
return
}
func bodyHtmxAttrParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
// exclude hx-delete
resp.Reader.Find("[hx-get],[hx-post],[hx-put],[hx-patch]").Each(func(i int, item *goquery.Selection) {
req := &navigation.Request{
RootHostname: resp.RootHostname,
Depth: resp.Depth,
Source: resp.Resp.Request.URL.String(),
Tag: "htmx",
}
if hxGet, ok := item.Attr("hx-get"); ok && hxGet != "" {
req.Method = http.MethodGet
req.URL = resp.AbsoluteURL(hxGet)
req.Attribute = "hx-get"
navigationRequests = append(navigationRequests, req)
}
if hxPost, ok := item.Attr(("hx-post")); ok && hxPost != "" {
req.Method = http.MethodPost
req.URL = resp.AbsoluteURL(hxPost)
req.Attribute = "hx-post"
navigationRequests = append(navigationRequests, req)
}
if hxPut, ok := item.Attr(("hx-put")); ok && hxPut != "" {
req.Method = http.MethodPut
req.URL = resp.AbsoluteURL(hxPut)
req.Attribute = "hx-put"
navigationRequests = append(navigationRequests, req)
}
if hxPatch, ok := item.Attr(("hx-patch")); ok && hxPatch != "" {
req.Method = http.MethodPatch
req.URL = resp.AbsoluteURL(hxPatch)
req.Attribute = "hx-patch"
navigationRequests = append(navigationRequests, req)
}
})
return
}
// -------------------------------------------------------------------------
// Begin JS Regex based parsers
// -------------------------------------------------------------------------
// scriptContentRegexParser parses script content endpoints from response
func scriptContentRegexParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("script").Each(func(i int, item *goquery.Selection) {
text := item.Text()
if text == "" {
return
}
endpoints := utils.ExtractRelativeEndpoints(text)
for _, item := range endpoints {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), "script", "text", resp))
}
})
return
}
// scriptJSFileRegexParser parses relative endpoints from js file pages
func scriptJSFileRegexParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
// Only process javascript file based on path or content type
// CSS, JS are supported for relative endpoint extraction.
contentType := resp.Resp.Header.Get("Content-Type")
if !stringsutil.HasSuffixAny(resp.Resp.Request.URL.Path, ".js", ".css") && !strings.Contains(contentType, "/javascript") {
return
}
endpointsItems := utils.ExtractRelativeEndpoints(string(resp.Body))
for _, item := range endpointsItems {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), "js", "regex", resp))
}
return
}
// bodyScrapeEndpointsParser parses scraped URLs from HTML body
func bodyScrapeEndpointsParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
endpoints := utils.ExtractBodyEndpoints(string(resp.Body))
for _, item := range endpoints {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), "html", "regex", resp))
}
return
}
// customFieldRegexParser parses custom regex from HTML body and header
func customFieldRegexParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
var customField = make(map[string][]string)
for _, v := range output.CustomFieldsMap {
results := []string{}
for _, re := range v.CompileRegex {
matches := [][]string{}
// read body
if v.Part == output.Body.ToString() || v.Part == output.Response.ToString() {
matches = re.FindAllStringSubmatch(string(resp.Body), -1)
}
// read header
if v.Part == output.Header.ToString() || v.Part == output.Response.ToString() {
for key, v := range resp.Resp.Header {
header := key + ": " + strings.Join(v, "\n")
headerMatches := re.FindAllStringSubmatch(header, -1)
matches = append(matches, headerMatches...)
}
}
for _, match := range matches {
if len(match) < (v.Group + 1) {
continue
}
matchString := match[v.Group]
results = append(results, matchString)
}
}
if len(results) > 0 {
customField[v.GetName()] = results
}
}
if len(customField) != 0 {
navigationRequests = append(navigationRequests, &navigation.Request{
Method: "GET",
URL: resp.Resp.Request.URL.String(),
Depth: resp.Depth,
CustomFields: customField,
})
}
return
}
================================================
FILE: pkg/engine/parser/parser_generic.go
================================================
//go:build !(386 || windows)
package parser
import (
"fmt"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/utils"
stringsutil "github.com/projectdiscovery/utils/strings"
)
type Options struct {
AutomaticFormFill bool
ScrapeJSLuiceResponses bool
ScrapeJSResponses bool
DisableRedirects bool
}
func (p *Parser) InitWithOptions(options *Options) {
if options.AutomaticFormFill {
*p = append(*p, responseParser{bodyParser, bodyFormTagParser})
}
if options.ScrapeJSLuiceResponses {
*p = append(*p, responseParser{bodyParser, scriptContentJsluiceParser})
*p = append(*p, responseParser{contentParser, scriptJSFileJsluiceParser})
}
if options.ScrapeJSResponses {
*p = append(*p, responseParser{bodyParser, scriptContentRegexParser})
*p = append(*p, responseParser{contentParser, scriptJSFileRegexParser})
*p = append(*p, responseParser{contentParser, bodyScrapeEndpointsParser})
}
if !options.DisableRedirects {
*p = append(*p, responseParser{headerParser, headerLocationParser})
}
}
// scriptContentJsluiceParser parses script content endpoints using jsluice from response
func scriptContentJsluiceParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
resp.Reader.Find("script").Each(func(i int, item *goquery.Selection) {
text := item.Text()
if text == "" {
return
}
endpointItems := utils.ExtractJsluiceEndpoints(text)
for _, item := range endpointItems {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item.Endpoint, resp.Resp.Request.URL.String(), "script", fmt.Sprintf("jsluice-%s", item.Type), resp))
}
})
return
}
// scriptJSFileJsluiceParser parses endpoints using jsluice from js file pages
func scriptJSFileJsluiceParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {
// Only process javascript file based on path or content type
// CSS, JS are supported for relative endpoint extraction.
contentType := resp.Resp.Header.Get("Content-Type")
if !stringsutil.HasSuffixAny(resp.Resp.Request.URL.Path, ".js", ".css") && !strings.Contains(contentType, "/javascript") {
return
}
// Skip common js libraries
if utils.IsPathCommonJSLibraryFile(resp.Resp.Request.URL.Path) {
return
}
endpointsItems := utils.ExtractJsluiceEndpoints(string(resp.Body))
for _, item := range endpointsItems {
navigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item.Endpoint, resp.Resp.Request.URL.String(), "js", fmt.Sprintf("jsluice-%s", item.Type), resp))
}
return
}
================================================
FILE: pkg/engine/parser/parser_nojs.go
================================================
//go:build windows || 386
package parser
type Options struct {
AutomaticFormFill bool
ScrapeJSLuiceResponses bool
ScrapeJSResponses bool
DisableRedirects bool
}
func (p *Parser) InitWithOptions(options *Options) {
if options.AutomaticFormFill {
*p = append(*p, responseParser{bodyParser, bodyFormTagParser})
}
if options.ScrapeJSResponses {
*p = append(*p, responseParser{bodyParser, scriptContentRegexParser})
*p = append(*p, responseParser{contentParser, scriptJSFileRegexParser})
*p = append(*p, responseParser{contentParser, bodyScrapeEndpointsParser})
}
if !options.DisableRedirects {
*p = append(*p, responseParser{headerParser, headerLocationParser})
}
}
================================================
FILE: pkg/engine/parser/parser_test.go
================================================
package parser
import (
"net/http"
"regexp"
"strings"
"testing"
"github.com/PuerkitoBio/goquery"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/output"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/stretchr/testify/require"
)
func TestHeaderParsers(t *testing.T) {
parsed, _ := urlutil.Parse("https://security-crawl-maze.app/headers/xyz/")
t.Run("content-location", func(t *testing.T) {
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{"Content-Location": []string{"/test/headers/content-location.found"}}}}
navigationRequests := headerContentLocationParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/headers/content-location.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("link", func(t *testing.T) {
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{"Link": []string{"; rel=\"preload\""}}}}
navigationRequests := headerLinkParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/headers/link.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("location", func(t *testing.T) {
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{"Location": []string{"http://security-crawl-maze.app/test/headers/location.found"}}}}
navigationRequests := headerLocationParser(resp)
require.Equal(t, "http://security-crawl-maze.app/test/headers/location.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("refresh", func(t *testing.T) {
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{"Refresh": []string{"999; url=/test/headers/refresh.found"}}}}
navigationRequests := headerRefreshParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/headers/refresh.found", navigationRequests[0].URL, "could not get correct url")
})
}
func TestBodyParsers(t *testing.T) {
parsed, _ := urlutil.Parse("https://security-crawl-maze.app/html/body/xyz/")
t.Run("a", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(""))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyATagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/a/href.found", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(" "))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodyATagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/a/ping.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("background", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(""))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyBackgroundTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/background.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("blockquote", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyBlockquoteCiteTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/blockquote/cite.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("area", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`
`))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyMapAreaPingTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/map/area/ping.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("audio", func(t *testing.T) {
t.Run("src", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyAudioTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/audio/src.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("source", func(t *testing.T) {
t.Run("src", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyAudioTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/audio/source/src.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("srcset", func(t *testing.T) {
var gotURL []string
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`
`))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
for _, navigationRequest := range bodyAudioTagParser(resp) {
gotURL = append(gotURL, navigationRequest.URL)
}
require.ElementsMatch(t, []string{
"https://security-crawl-maze.app/test/html/body/audio/source/srcset1x.found",
"https://security-crawl-maze.app/test/html/body/audio/source/srcset2x.found",
}, gotURL, "could not get correct url")
})
})
})
t.Run("img", func(t *testing.T) {
t.Run("dynsrc", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyImgTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/img/dynsrc.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("longdesc", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyImgTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/img/longdesc.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("lowsrc", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyImgTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/img/lowsrc.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("src", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyImgTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/img/src.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("srcset", func(t *testing.T) {
var gotURL []string
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
for _, navigationResponse := range bodyImgTagParser(resp) {
gotURL = append(gotURL, navigationResponse.URL)
}
require.ElementsMatch(t, []string{
"https://security-crawl-maze.app/test/html/body/img/srcset1x.found",
"https://security-crawl-maze.app/test/html/body/img/srcset2x.found",
}, gotURL, "could not get correct url")
})
})
t.Run("html-body", func(t *testing.T) {
// TODO: Fix parsing
//
// parsed, _ = url.Parse("https://security-crawl-maze.app/html/body/frameset/frame/src.html")
// var gotURL []string
// resp := navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Body: []byte(`
// The test contains an inline string with known extension - /string-known-extension.pdf
// The test contains an inline string - ./test/html/misc/string/dot-slash-prefix.found
// The test contains an inline string - ../test/html/misc/string/dot-dot-slash-prefix.found
// The test contains an inline string - http://security-crawl-maze.app/test/html/misc/string/url-string.found
//
`), Options: &types.CrawlerOptions{Options: &types.Options{ScrapeJSResponses: true}}}
// bodyScrapeEndpointsParser(resp, func(resp navigation.Request) {
// gotURL = append(gotURL, resp.URL)
// })
// require.ElementsMatch(t, []string{
// "https://security-crawl-maze.app/test/string-known-extension.pdf",
// "https://security-crawl-maze.app/test/html/misc/string/dot-slash-prefix.found",
// "https://security-crawl-maze.app/test/html/misc/string/dot-dot-slash-prefix.found",
// "http://security-crawl-maze.app/test/html/misc/string/url-string.found",
// }, gotURL, "could not get correct url")
})
t.Run("object", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyObjectTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/object/data.found", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(` `))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodyObjectTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/object/codebase.found", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`
`))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodyObjectTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/object/param/value.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("svg", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`
`))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodySvgTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/svg/image/xlink.found", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`
`))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodySvgTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/svg/script/xlink.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("table", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyTableTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/table/background.found", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(``))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodyTableTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/table/td/background.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("video", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyVideoTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/video/poster.found", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(` `))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodyVideoTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/video/src.found", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`
`))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodyVideoTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/video/track/src.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("applet", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyAppletTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/applet/archive.found", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(` `))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodyAppletTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/applet/codebase.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("link", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyLinkHrefTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/css/font-face.css", navigationRequests[0].URL, "could not get correct url")
documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(` `))
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests = bodyLinkHrefTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/head/link/href.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("base", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyBaseHrefTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/head/base/href.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("manifest", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(``))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyHtmlManifestTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/manifest.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("doctype", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`
`))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyHtmlDoctypeTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/doctype.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("import", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(` `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyImportImplementationTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/head/import/implementation.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("embed", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyEmbedTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/embed/src.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("frame", func(t *testing.T) {
// var gotURL string
// // TODO: Fix test
// documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`
//
//
// CrawlMaze - Testbed for Web Crawlers - frame tag
// src attribute
//
//
// `))
// resp := navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
// bodyFrameTagParser(resp, func(resp navigation.Request) {
// gotURL = resp.URL
// })
// require.Equal(t, "https://security-crawl-maze.app/test/html/body/frameset/frame/src.found", gotURL, "could not get correct url")
})
t.Run("iframe", func(t *testing.T) {
t.Run("src", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(""))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyIframeTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/iframe/src.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("srcdoc", func(t *testing.T) {
//var gotURL string
//documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(""))
//resp := navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
//bodyIframeTagParser(resp, func(resp navigation.Request) {
// gotURL = resp.URL
//})
//require.Equal(t, "https://security-crawl-maze.app/test/html/body/iframe/srcdoc.found", gotURL, "could not get correct url")
})
})
t.Run("input", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyInputSrcTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/input/src.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("isindex", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyIsindexActionTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/isindex/action.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("script", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(""))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyScriptSrcTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/script/src.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("button", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader("CLICKME "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyButtonFormactionTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/form/button/formaction.found", navigationRequests[0].URL, "could not get correct url")
})
t.Run("form", func(t *testing.T) {
t.Run("get", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyFormTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/form/action-get.found?test1=test&test2=test", navigationRequests[0].URL, "could not get correct url")
})
t.Run("post", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyFormTagParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/body/form/action-post.found", navigationRequests[0].URL, "could not get correct url")
require.Equal(t, "POST", navigationRequests[0].Method, "could not get correct method")
})
})
t.Run("meta", func(t *testing.T) {
// var gotURL string
// documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(" "))
// resp := navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
// bodyMetaContentTagParser(resp, func(resp navigation.Request) {
// gotURL = resp.URL
// })
// require.Equal(t, "https://security-crawl-maze.app/test/html/head/meta/content-redirect.found", gotURL, "could not get correct url")
//
// documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(` `))
// resp = navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
// bodyMetaContentTagParser(resp, func(resp navigation.Request) {
// gotURL = resp.URL
// })
// require.Equal(t, "https://security-crawl-maze.app/test/html/head/meta/content-csp.found", gotURL, "could not get correct url")
//
// documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(` `))
// resp = navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
// bodyMetaContentTagParser(resp, func(resp navigation.Request) {
// gotURL = resp.URL
// })
// require.Equal(t, "https://security-crawl-maze.app/test/html/head/meta/content-pinned-websites.found", gotURL, "could not get correct url")
//
// documentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(` `))
// resp = navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
// bodyMetaContentTagParser(resp, func(resp navigation.Request) {
// gotURL = resp.URL
// })
// require.Equal(t, "https://security-crawl-maze.app/test/html/head/meta/content-reading-view.found", gotURL, "could not get correct url")
})
}
func TestScriptParsers(t *testing.T) {
parsed, _ := urlutil.Parse("https://security-crawl-maze.app/html/script/xyz/")
t.Run("content", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(""))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := scriptContentRegexParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/script/content.do", navigationRequests[0].URL, "could not get correct url")
})
t.Run("js", func(t *testing.T) {
parsed, _ = urlutil.Parse("https://security-crawl-maze.app/html/script/xyz/data.js")
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Body: "var endpoint='/test/html/script/body.do';"}
navigationRequests := scriptJSFileRegexParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/script/body.do", navigationRequests[0].URL, "could not get correct url")
parsed, _ = urlutil.Parse("https://security-crawl-maze.app/html/script/xyz/")
resp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{"Content-Type": []string{"application/javascript"}}}, Body: "var endpoint='/test/html/script/body-content-type.do';"}
navigationRequests = scriptJSFileRegexParser(resp)
require.Equal(t, "https://security-crawl-maze.app/test/html/script/body-content-type.do", navigationRequests[0].URL, "could not get correct url")
})
}
func TestRegexBodyParsers(t *testing.T) {
parsed, _ := urlutil.Parse("https://security-crawl-maze.app/contact")
t.Run("regexbody", func(t *testing.T) {
output.CustomFieldsMap = make(map[string]output.CustomFieldConfig)
resp := &navigation.Response{
Resp: &http.Response{Request: &http.Request{URL: parsed.URL}},
Depth: 0,
Body: "some content contact@example.com",
}
// set required regex
output.CustomFieldsMap["email"] = output.CustomFieldConfig{
Name: "email",
Type: "regex",
Part: "body",
CompileRegex: []*regexp.Regexp{regexp.MustCompile(`([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)`)},
}
navigationRequests := customFieldRegexParser(resp)
var requireFields = map[string][]string{"email": {"contact@example.com"}}
require.Equal(t, requireFields, navigationRequests[0].CustomFields, "could not get correct url")
})
t.Run("regexheader", func(t *testing.T) {
output.CustomFieldsMap = make(map[string]output.CustomFieldConfig)
resp := &navigation.Response{
Resp: &http.Response{Request: &http.Request{URL: parsed.URL},
Header: http.Header{
"server": []string{"ECS (dcb/7F84)"},
},
},
}
// set required regex
output.CustomFieldsMap["server"] = output.CustomFieldConfig{
Name: "server",
Type: "regex",
Part: "header",
CompileRegex: []*regexp.Regexp{regexp.MustCompile(`server: ECS`)},
}
navigationRequests := customFieldRegexParser(resp)
var requireFields = map[string][]string{"server": {"server: ECS"}}
require.Equal(t, requireFields, navigationRequests[0].CustomFields, "could not get correct url")
})
t.Run("regexresponse", func(t *testing.T) {
output.CustomFieldsMap = make(map[string]output.CustomFieldConfig)
resp := &navigation.Response{
Resp: &http.Response{Request: &http.Request{URL: parsed.URL},
Header: http.Header{
"server": []string{"ECS (dcb/7F84)"},
},
},
Body: "some content contact@example.com",
}
// set required regex
output.CustomFieldsMap["server"] = output.CustomFieldConfig{
Name: "server",
Type: "regex",
Part: "response",
CompileRegex: []*regexp.Regexp{regexp.MustCompile(`ECS`)},
}
output.CustomFieldsMap["email"] = output.CustomFieldConfig{
Name: "email",
Type: "regex",
Part: "response",
CompileRegex: []*regexp.Regexp{regexp.MustCompile(`([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)`)},
}
navigationRequests := customFieldRegexParser(resp)
var requireFields = map[string][]string{"server": {"ECS"}, "email": {"contact@example.com"}}
require.Equal(t, requireFields, navigationRequests[0].CustomFields, "could not get correct url")
})
}
func TestHtmxBodyParser(t *testing.T) {
parsed, _ := urlutil.Parse("https://htmx.org/examples/")
t.Run("hx-get", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`Click To Edit `))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyHtmxAttrParser(resp)
require.Equal(t, "https://htmx.org/contact/1/edit", navigationRequests[0].URL, "could not get correct url")
require.Equal(t, "GET", navigationRequests[0].Method, "could not get correct method")
})
t.Run("hx-post", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(``))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyHtmxAttrParser(resp)
require.Equal(t, "https://htmx.org/users", navigationRequests[0].URL, "could not get correct url")
require.Equal(t, "POST", navigationRequests[0].Method, "could not get correct method")
})
t.Run("hx-put", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(``))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyHtmxAttrParser(resp)
require.Equal(t, "https://htmx.org/account", navigationRequests[0].URL, "could not get correct url")
require.Equal(t, "PUT", navigationRequests[0].Method, "could not get correct method")
})
t.Run("hx-patch", func(t *testing.T) {
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(``))
resp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}
navigationRequests := bodyHtmxAttrParser(resp)
require.Equal(t, "https://htmx.org/account", navigationRequests[0].URL, "could not get correct url")
require.Equal(t, "PATCH", navigationRequests[0].Method, "could not get correct method")
})
}
func TestDataURIFiltering(t *testing.T) {
parsed, _ := urlutil.Parse("https://example.com/test")
t.Run("data-uri-filtering", func(t *testing.T) {
// Test various HTML elements with data URIs to ensure they're filtered out by ParseResponse
htmlContent := `
Data Link
Email Link
JS Link
`
documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
resp := &navigation.Response{
Resp: &http.Response{Request: &http.Request{URL: parsed.URL}},
Reader: documentReader,
}
// Test the main ParseResponse function to ensure all invalid URIs are filtered out
responseParser := NewResponseParser()
navigationRequests := responseParser.ParseResponse(resp)
for _, req := range navigationRequests {
require.False(t, strings.HasPrefix(req.URL, "data:"),
"Found data URI in ParseResponse results: %s", req.URL)
require.False(t, strings.HasPrefix(req.URL, "mailto:"),
"Found mailto URI in ParseResponse results: %s", req.URL)
require.False(t, strings.HasPrefix(req.URL, "javascript:"),
"Found javascript URI in ParseResponse results: %s", req.URL)
}
// Ensure we have 0 requests since all should be filtered
require.Equal(t, 0, len(navigationRequests), "Expected all invalid URIs to be filtered out")
})
}
================================================
FILE: pkg/engine/standard/crawl.go
================================================
package standard
import (
"bytes"
"context"
"io"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/projectdiscovery/katana/pkg/engine/common"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/utils"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
mapsutil "github.com/projectdiscovery/utils/maps"
)
// makeRequest makes a request to a URL returning a response interface.
func (c *Crawler) makeRequest(s *common.CrawlSession, request *navigation.Request) (*navigation.Response, error) {
response := &navigation.Response{
Depth: request.Depth + 1,
RootHostname: s.Hostname,
}
ctx := context.WithValue(s.Ctx, navigation.Depth{}, request.Depth)
httpReq, err := http.NewRequestWithContext(ctx, request.Method, request.URL, nil)
if err != nil {
return response, err
}
if request.Body != "" && request.Method != "GET" {
httpReq.Body = io.NopCloser(strings.NewReader(request.Body))
}
req, err := retryablehttp.FromRequest(httpReq)
if err != nil {
return response, err
}
req.Header.Set("User-Agent", utils.WebUserAgent())
// Set the headers for the request.
for k, v := range request.Headers {
req.Header.Set(k, v)
if k == "Host" {
req.Host = v
}
}
for k, v := range c.Headers {
req.Header.Set(k, v)
if k == "Host" {
req.Host = v
}
}
// Apply cookies
if c.Jar != nil {
cookies := c.Jar.Cookies(req.Request.URL)
for _, cookie := range cookies {
req.AddCookie(cookie)
}
}
resp, err := s.HttpClient.Do(req)
if resp != nil {
defer func() {
if resp.Body != nil && resp.StatusCode != http.StatusSwitchingProtocols {
_, _ = io.Copy(io.Discard, resp.Body)
}
_ = resp.Body.Close()
}()
}
// Collect cookies from the response
if c.Jar != nil && resp != nil {
c.Jar.SetCookies(req.Request.URL, resp.Cookies())
}
rawRequestBytes, _ := req.Dump()
request.Raw = string(rawRequestBytes)
if err != nil {
return response, err
}
// If the response is empty, perform a defensive return.
if resp == nil {
return response, errkit.New("standard: nil response from http client")
}
if resp.StatusCode == http.StatusSwitchingProtocols {
return response, nil
}
limitReader := io.LimitReader(resp.Body, int64(c.Options.Options.BodyReadSize))
data, err := io.ReadAll(limitReader)
if err != nil {
return response, err
}
// Skip unique content filtering if disabled
if !c.Options.Options.DisableUniqueFilter {
if !c.Options.UniqueFilter.UniqueContent(data) {
return &navigation.Response{}, nil
}
}
if c.Options.Wappalyzer != nil {
technologies := c.Options.Wappalyzer.Fingerprint(resp.Header, data)
response.Technologies = mapsutil.GetKeys(technologies)
}
response.KnowledgeBase = c.Options.ClassifyPage(string(data))
// Restore the read data to resp.Body for further use.
resp.Body = io.NopCloser(strings.NewReader(string(data)))
response.Body = string(data)
response.Resp = resp
// First, attempt to parse using goquery. If the parsing fails, simply return safely (to avoid accessing response.Reader before checking err).
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(data))
if err != nil {
// Even if the parsing fails, try to attach the original response for debugging purposes.
rawResponseBytes, _ := httputil.DumpResponse(resp, true)
response.Raw = string(rawResponseBytes)
return response, errkit.Wrap(err, "standard: could not make document from reader")
}
// Only set the `response.Reader` and its URL after the parsing is successful to avoid accessing a nil pointer.
response.Reader = doc
response.Reader.Url, _ = url.Parse(request.URL)
response.StatusCode = resp.StatusCode
response.Headers = utils.FlattenHeaders(resp.Header)
if c.Options.Options.FormExtraction {
response.Forms = append(response.Forms, utils.ParseFormFields(response.Reader)...)
}
// Use the actual length of the read data as ContentLength
resp.ContentLength = int64(len(data))
response.ContentLength = resp.ContentLength
rawResponseBytes, _ := httputil.DumpResponse(resp, true)
response.Raw = string(rawResponseBytes)
return response, nil
}
================================================
FILE: pkg/engine/standard/doc.go
================================================
// Package standard implements the functionality for a non-headless crawler.
// It uses net/http for making requests and goquery for scraping web page HTML.
package standard
================================================
FILE: pkg/engine/standard/standard.go
================================================
package standard
import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/engine/common"
"github.com/projectdiscovery/katana/pkg/types"
"github.com/projectdiscovery/utils/errkit"
)
// Crawler is a standard crawler instance
type Crawler struct {
*common.Shared
}
// New returns a new standard crawler instance
func New(options *types.CrawlerOptions) (*Crawler, error) {
shared, err := common.NewShared(options)
if err != nil {
return nil, errkit.Wrap(err, "standard")
}
return &Crawler{Shared: shared}, nil
}
// Close closes the crawler process
func (c *Crawler) Close() error {
return nil
}
// Crawl crawls a URL with the specified options
func (c *Crawler) Crawl(rootURL string) error {
crawlSession, err := c.NewCrawlSessionWithURL(rootURL)
if err != nil {
return errkit.Wrap(err, "standard")
}
defer crawlSession.CancelFunc()
gologger.Info().Msgf("Started standard crawling for => %v", rootURL)
if err := c.Do(crawlSession, c.makeRequest); err != nil {
return errkit.Wrap(err, "standard")
}
return nil
}
================================================
FILE: pkg/navigation/request.go
================================================
package navigation
import (
"net/http"
"strings"
)
// Depth is the depth of a navigation
type Depth struct{}
// Request is a navigation request for the crawler
type Request struct {
Method string `json:"method,omitempty"`
URL string `json:"endpoint,omitempty"`
Body string `json:"body,omitempty"`
Depth int `json:"-"`
SkipValidation bool `json:"-"`
Headers map[string]string `json:"headers,omitempty"`
Tag string `json:"tag,omitempty"`
Attribute string `json:"attribute,omitempty"`
RootHostname string `json:"-"`
Source string `json:"source,omitempty"`
CustomFields map[string][]string `json:"custom_fields,omitempty"`
Raw string `json:"raw,omitempty"`
}
// RequestURL returns the request URL for the navigation
func (n *Request) RequestURL() string {
switch n.Method {
case "GET":
return n.URL
case "POST":
builder := &strings.Builder{}
builder.WriteString(n.URL)
builder.WriteString(":")
builder.WriteString(n.Body)
builtURL := builder.String()
return builtURL
}
return ""
}
// newNavigationRequestURL generates a navigation request from a relative URL
func NewNavigationRequestURLFromResponse(path, source, tag, attribute string, resp *Response) *Request {
requestURL := resp.AbsoluteURL(path)
request := &Request{
Method: http.MethodGet,
URL: requestURL,
RootHostname: resp.RootHostname,
Depth: resp.Depth,
Source: source,
Attribute: attribute,
Tag: tag,
}
return request
}
================================================
FILE: pkg/navigation/response.go
================================================
package navigation
import (
"net/http"
"strings"
"github.com/PuerkitoBio/goquery"
jsoniter "github.com/json-iterator/go"
)
type Headers map[string]string
type Form struct {
Method string `json:"method,omitempty"`
Action string `json:"action,omitempty"`
Enctype string `json:"enctype,omitempty"`
Parameters []string `json:"parameters,omitempty"`
}
func (h *Headers) MarshalJSON() ([]byte, error) {
hCopy := make(Headers)
for k, v := range *h {
k := strings.ToLower(k)
hCopy[k] = v
}
return jsoniter.Marshal(hCopy)
}
// Response is a response generated from crawler navigation
type Response struct {
Resp *http.Response `json:"-"`
Depth int `json:"-"`
Reader *goquery.Document `json:"-"`
StatusCode int `json:"status_code,omitempty"`
Headers Headers `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
ContentLength int64 `json:"content_length,omitempty"`
RootHostname string `json:"-"`
Technologies []string `json:"technologies,omitempty"`
Raw string `json:"raw,omitempty"`
Forms []Form `json:"forms,omitempty"`
XhrRequests []Request `json:"xhr_requests,omitempty"`
StoredResponsePath string `json:"stored_response_path,omitempty"`
KnowledgeBase map[string]any `json:"knowledgebase,omitempty"`
}
func (n Response) AbsoluteURL(path string) string {
if strings.HasPrefix(path, "#") {
return ""
}
absURL, err := n.Resp.Request.URL.Parse(path)
if err != nil {
return ""
}
absURL.Fragment = ""
if absURL.Scheme == "//" {
absURL.Scheme = n.Resp.Request.URL.Scheme
}
final := absURL.String()
return final
}
func (n Response) IsRedirect() bool {
return n.StatusCode >= 300 && n.StatusCode <= 399
}
================================================
FILE: pkg/output/custom_field.go
================================================
package output
import (
"os"
"path/filepath"
"regexp"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
sliceutil "github.com/projectdiscovery/utils/slice"
"gopkg.in/yaml.v2"
)
// CustomFieldsMap is the global custom field data instance
// it is used for parsing the header and body of request
var CustomFieldsMap = make(map[string]CustomFieldConfig)
type Part string
const (
// RequestPart is the part of request
Header Part = "header"
Body Part = "body"
Response Part = "response"
)
// CustomFieldConfig contains suggestions for field filling
type CustomFieldConfig struct {
Name string `yaml:"name,omitempty"`
Type string `yaml:"type,omitempty"`
Part string `yaml:"part,omitempty"`
Group int `yaml:"group,omitempty"`
Regex []string `yaml:"regex,omitempty"`
CompileRegex []*regexp.Regexp `yaml:"-"`
}
var DefaultFieldConfigData = []CustomFieldConfig{
{
Name: "email",
Type: "regex",
Part: Response.ToString(),
Regex: []string{`([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)`},
},
}
func (c *CustomFieldConfig) SetCompiledRegexp(r *regexp.Regexp) {
c.CompileRegex = append(c.CompileRegex, r)
}
func (c *CustomFieldConfig) GetName() string {
return c.Name
}
func parseCustomFieldName(filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return errkit.Wrap(err, "customfield: could not read field config")
}
defer func() {
if err := file.Close(); err != nil {
gologger.Error().Msgf("Error closing file: %v\n", err)
}
}()
var data []CustomFieldConfig
if err := yaml.NewDecoder(file).Decode(&data); err != nil {
return errkit.Wrap(err, "customfield: could not decode field config")
}
passedCustomFieldMap := make(map[string]CustomFieldConfig)
for _, item := range data {
if !regexp.MustCompile(`^[A-Za-z0-9_-]+$`).MatchString(item.Name) {
return errkit.Newf("customfield: wrong custom field name %s", item.Name)
}
// check custom field name is pre-defined or not
if sliceutil.Contains(FieldNames, item.Name) {
return errkit.Newf("customfield: could not register custom field. \"%s\" already pre-defined field", item.Name)
}
// check custom field name should be unique
if _, ok := passedCustomFieldMap[item.Name]; ok {
return errkit.Newf("customfield: could not register custom field. \"%s\" custom field already exists", item.Name)
}
passedCustomFieldMap[item.Name] = item
}
return nil
}
func loadCustomFields(filePath string, fields string) error {
var err error
file, err := os.Open(filePath)
if err != nil {
return errkit.Wrap(err, "customfield: could not read field config")
}
defer func() {
if err := file.Close(); err != nil {
gologger.Error().Msgf("Error closing file: %v\n", err)
}
}()
var data []CustomFieldConfig
// read the field config file
if err := yaml.NewDecoder(file).Decode(&data); err != nil {
return errkit.Wrap(err, "customfield: could not decode field config")
}
for _, item := range data {
for _, rg := range item.Regex {
regex, err := regexp.Compile(rg)
if err != nil {
return errkit.Wrap(err, "customfield: could not parse regex in field config")
}
item.SetCompiledRegexp(regex)
}
if item.Part == "" {
item.Part = Response.ToString()
}
CustomFieldsMap[item.Name] = item
}
return nil
}
func initCustomFieldConfigFile() (string, error) {
homedir, err := os.UserHomeDir()
if err != nil {
return "", errkit.Wrap(err, "customfield: could not get home directory")
}
defaultConfig := filepath.Join(homedir, ".config", "katana", "field-config.yaml")
if fileutil.FileExists(defaultConfig) {
return defaultConfig, nil
}
if err := os.MkdirAll(filepath.Dir(defaultConfig), 0775); err != nil {
return "", err
}
customFieldConfig, err := os.Create(defaultConfig)
if err != nil {
return "", errkit.Wrap(err, "customfield: could not get home directory")
}
defer func() {
if err := customFieldConfig.Close(); err != nil {
gologger.Error().Msgf("Error closing custom field config: %v\n", err)
}
}()
err = yaml.NewEncoder(customFieldConfig).Encode(DefaultFieldConfigData)
if err != nil {
return "", err
}
return defaultConfig, nil
}
func (p Part) ToString() string {
return string(p)
}
================================================
FILE: pkg/output/error.go
================================================
package output
import "time"
type Error struct {
Timestamp time.Time `json:"timestamp,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Source string `json:"source,omitempty"`
Error string `json:"error,omitempty"`
}
================================================
FILE: pkg/output/fields.go
================================================
package output
import (
"fmt"
"net/url"
"os"
"path"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/utils/errkit"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
"golang.org/x/net/publicsuffix"
)
// FieldNames is a list of supported field names
var FieldNames = []string{
"url",
"path",
"fqdn",
"rdn",
"rurl",
"qurl",
"qpath",
"file",
"ufile",
"key",
"value",
"kv",
"dir",
"udir",
}
type fieldOutput struct {
field string
value string
}
// validateFieldNames validates provided field names
func validateFieldNames(names string) error {
parts := strings.Split(names, ",")
if len(parts) == 0 {
return errkit.Newf("customfield: no field names provided: %s", names)
}
uniqueFields := make(map[string]struct{})
for _, field := range FieldNames {
uniqueFields[field] = struct{}{}
}
for _, field := range CustomFieldsMap {
uniqueFields[field.Name] = struct{}{}
}
for _, part := range parts {
if _, ok := uniqueFields[part]; !ok {
return errkit.Newf("customfield: invalid field %s specified: %s", part, names)
}
}
return nil
}
// storeFields stores fields for a result into individual files
// based on name.
func storeFields(output *Result, storeFields []string) {
parsed, err := urlutil.Parse(output.Request.URL)
if err != nil {
gologger.Warning().Msgf("storeFields: failed to parse url %v got %v", output.Request.URL, err)
return
}
hostname := parsed.Hostname()
etld, _ := publicsuffix.EffectiveTLDPlusOne(hostname)
rootURL := fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host)
for _, field := range storeFields {
if result := getValueForField(output, parsed.URL, hostname, etld, rootURL, field); result != "" {
appendToFileField(parsed.URL, field, result)
}
if _, ok := CustomFieldsMap[field]; ok {
results := getValueForCustomField(output)
for _, result := range results {
appendToFileField(parsed.URL, result.field, result.value)
}
}
}
}
func appendToFileField(parsed *url.URL, field, data string) {
file, err := os.OpenFile(path.Join(storeFieldDir, fmt.Sprintf("%s_%s_%s.txt", parsed.Scheme, parsed.Hostname(), field)), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
return
}
defer func() {
if err := file.Close(); err != nil {
gologger.Error().Msgf("Error closing file: %v\n", err)
}
}()
_, _ = file.WriteString(data)
_, _ = file.Write([]byte("\n"))
}
// formatField formats output results based on fields from fieldNames
func formatField(output *Result, fields string) []fieldOutput {
var svalue []fieldOutput
parsed, _ := urlutil.Parse(output.Request.URL)
if parsed == nil {
return svalue
}
queryBoth := []string{}
queryKeys := []string{}
queryValues := []string{}
parsed.Query().Iterate(func(k string, v []string) bool {
for _, value := range v {
queryBoth = append(queryBoth, strings.Join([]string{k, value}, "="))
}
queryKeys = append(queryKeys, k)
queryValues = append(queryValues, v...)
return true
})
for _, f := range stringsutil.SplitAny(fields, ",") {
switch f {
case "url":
svalue = append(svalue, fieldOutput{field: "url", value: output.Request.URL})
case "rdn":
hostname := parsed.Hostname()
etld, _ := publicsuffix.EffectiveTLDPlusOne(hostname)
svalue = append(svalue, fieldOutput{field: "rdn", value: etld})
case "path":
if parsed.Path != "" {
svalue = append(svalue, fieldOutput{field: "path", value: parsed.Path})
}
case "fqdn":
svalue = append(svalue, fieldOutput{field: "fqdn", value: parsed.Hostname()})
case "rurl":
svalue = append(svalue, fieldOutput{field: "rurl", value: fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host)})
case "qpath":
if len(queryKeys) > 0 {
svalue = append(svalue, fieldOutput{field: "qpath", value: fmt.Sprintf("%s?%s", parsed.Path, parsed.Query().Encode())})
}
case "qurl":
if len(queryKeys) > 0 {
svalue = append(svalue, fieldOutput{field: "qurl", value: output.Request.URL})
}
case "key":
if len(queryKeys) > 0 || len(queryValues) > 0 || len(queryBoth) > 0 {
for _, k := range queryKeys {
svalue = append(svalue, fieldOutput{field: "key", value: k})
}
}
case "kv":
if len(queryKeys) > 0 || len(queryValues) > 0 || len(queryBoth) > 0 {
for _, k := range queryBoth {
svalue = append(svalue, fieldOutput{field: "kv", value: k})
}
}
case "value":
if len(queryKeys) > 0 || len(queryValues) > 0 || len(queryBoth) > 0 {
for _, k := range queryValues {
svalue = append(svalue, fieldOutput{field: "value", value: k})
}
}
case "file":
if parsed.Path != "" && parsed.Path != "/" {
basePath := path.Base(parsed.Path)
if strings.Contains(basePath, ".") {
svalue = append(svalue, fieldOutput{field: "file", value: basePath})
}
}
case "ufile":
if parsed.Path != "" && parsed.Path != "/" {
basePath := path.Base(parsed.Path)
if strings.Contains(basePath, ".") {
svalue = append(svalue, fieldOutput{field: "ufile", value: parsed.String()})
}
}
case "udir":
if parsed.Path != "" && parsed.Path != "/" {
if strings.Contains(parsed.Path[1:], "/") {
directory := parsed.Path[:strings.LastIndex(parsed.Path[1:], "/")+2]
rootURL := fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host)
svalue = append(svalue, fieldOutput{field: "udir", value: fmt.Sprintf("%s%s", rootURL, directory)})
}
}
case "dir":
if parsed.Path != "" && parsed.Path != "/" {
if strings.Contains(parsed.Path[1:], "/") {
directory := parsed.Path[:strings.LastIndex(parsed.Path[1:], "/")+2]
svalue = append(svalue, fieldOutput{field: "dir", value: directory})
}
}
default:
if v, ok := output.Request.CustomFields[f]; ok {
for _, r := range v {
svalue = append(svalue, fieldOutput{field: f, value: r})
}
}
}
}
return svalue
}
// getValueForField returns value for a field
func getValueForField(output *Result, parsed *url.URL, hostname, rdn, rurl, field string) string {
switch field {
case "url":
return output.Request.URL
case "path":
return parsed.Path
case "fqdn":
return hostname
case "rdn":
return rdn
case "rurl":
return rurl
case "ufile":
basePath := path.Base(parsed.Path)
if parsed.Path != "" && parsed.Path != "/" && strings.Contains(basePath, ".") {
return parsed.String()
}
case "file":
basePath := path.Base(parsed.Path)
if parsed.Path != "" && parsed.Path != "/" && strings.Contains(basePath, ".") {
return basePath
}
case "dir":
if parsed.Path != "" && parsed.Path != "/" && strings.Contains(parsed.Path[1:], "/") {
return parsed.Path[:strings.LastIndex(parsed.Path[1:], "/")+2]
}
case "udir":
if parsed.Path != "" && parsed.Path != "/" && strings.Contains(parsed.Path[1:], "/") {
return fmt.Sprintf("%s%s", rurl, parsed.Path[:strings.LastIndex(parsed.Path[1:], "/")+2])
}
case "qpath":
if len(parsed.Query()) > 0 {
return fmt.Sprintf("%s?%s", parsed.Path, parsed.Query().Encode())
}
case "qurl":
if len(parsed.Query()) > 0 {
return parsed.String()
}
case "key":
values := make([]string, 0, len(parsed.Query()))
for k := range parsed.Query() {
values = append(values, k)
}
return strings.Join(values, "\n")
case "value":
values := make([]string, 0, len(parsed.Query()))
for _, v := range parsed.Query() {
values = append(values, v...)
}
return strings.Join(values, "\n")
case "kv":
values := make([]string, 0, len(parsed.Query()))
for k, v := range parsed.Query() {
for _, value := range v {
values = append(values, strings.Join([]string{k, value}, "="))
}
}
return strings.Join(values, "\n")
}
return ""
}
func getValueForCustomField(output *Result) []fieldOutput {
var svalue []fieldOutput
for k, v := range output.Request.CustomFields {
for _, r := range v {
svalue = append(svalue, fieldOutput{field: k, value: r})
}
}
return svalue
}
================================================
FILE: pkg/output/fields_test.go
================================================
package output
import (
"testing"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/stretchr/testify/require"
)
func TestValidateFieldNames(t *testing.T) {
err := validateFieldNames("fqdn")
require.Nil(t, err, "got error with valid field")
err = validateFieldNames("")
require.Error(t, err, "got no error with blank field")
err = validateFieldNames("invalid")
require.Error(t, err, "got no error with invalid field")
}
func TestFormatField(t *testing.T) {
url := "https://policies.google.com/terms/file.php?hl=en-IN&fg=1"
tests := []struct {
url string
fields string
result []fieldOutput
}{
{url, "url", []fieldOutput{{"url", url}}},
{url, "path", []fieldOutput{{"path", "/terms/file.php"}}},
{url, "fqdn", []fieldOutput{{"fqdn", "policies.google.com"}}},
{url, "rdn", []fieldOutput{{"rdn", "google.com"}}},
{url, "rurl", []fieldOutput{{"rurl", "https://policies.google.com"}}},
{url, "file", []fieldOutput{{"file", "file.php"}}},
{url, "key", []fieldOutput{{"key", "hl"}, {"key", "fg"}}},
{url, "kv", []fieldOutput{{"kv", "hl=en-IN"}, {"kv", "fg=1"}}},
{url, "value", []fieldOutput{{"value", "en-IN"}, {"value", "1"}}},
{url, "dir", []fieldOutput{{"dir", "/terms/"}}},
{url, "udir", []fieldOutput{{"udir", "https://policies.google.com/terms/"}}},
}
for _, test := range tests {
result := formatField(&Result{Request: &navigation.Request{URL: test.url}}, test.fields)
require.ElementsMatch(t, test.result, result, "could not equal value")
}
}
================================================
FILE: pkg/output/file_writer.go
================================================
package output
import (
"bufio"
"os"
)
// fileWriter is a concurrent file based output writer.
type fileWriter struct {
file *os.File
writer *bufio.Writer
}
// NewFileOutputWriter creates a new buffered writer for a file
func newFileOutputWriter(file string) (*fileWriter, error) {
output, err := os.Create(file)
if err != nil {
return nil, err
}
return &fileWriter{file: output, writer: bufio.NewWriter(output)}, nil
}
// WriteString writes an output to the underlying file
func (w *fileWriter) Write(data []byte) error {
_, err := w.writer.Write(data)
if err != nil {
return err
}
_, err = w.writer.WriteRune('\n')
return err
}
// Close closes the underlying writer flushing everything to disk
func (w *fileWriter) Close() error {
_ = w.writer.Flush()
//nolint:errcheck // we don't care whether sync failed or succeeded.
w.file.Sync()
return w.file.Close()
}
================================================
FILE: pkg/output/format_json.go
================================================
package output
import (
jsoniter "github.com/json-iterator/go"
"github.com/projectdiscovery/utils/structs"
)
// formatJSON formats the output for json based formatting
func (w *StandardWriter) formatJSON(output *Result) ([]byte, error) {
finalOrdMap, err := structs.FilterStructToMap(*output, nil, w.excludeOutputFields)
if err != nil {
return nil, err
}
if _, ok := finalOrdMap.Get("request"); ok && output.Request != nil {
reqOrdMap, err := structs.FilterStructToMap(*output.Request, nil, w.excludeOutputFields)
if err != nil {
return nil, err
}
if reqOrdMap.Len() > 0 {
finalOrdMap.Set("request", reqOrdMap)
} else {
finalOrdMap.Delete("request")
}
}
if _, ok := finalOrdMap.Get("response"); ok && output.Response != nil {
respOrdMap, err := structs.FilterStructToMap(*output.Response, nil, w.excludeOutputFields)
if err != nil {
return nil, err
}
if respOrdMap.Len() > 0 {
finalOrdMap.Set("response", respOrdMap)
} else {
finalOrdMap.Delete("response")
}
}
return jsoniter.Marshal(finalOrdMap)
}
================================================
FILE: pkg/output/format_screen.go
================================================
package output
import (
"bytes"
"fmt"
"strconv"
)
// formatScreen formats the output for showing on screen.
func (w *StandardWriter) formatScreen(output *Result) ([]byte, error) {
builder := &bytes.Buffer{}
if w.fields != "" {
result := formatField(output, w.fields)
for _, fop := range result {
if w.verbose {
builder.WriteRune('[')
builder.WriteString(w.aurora.Blue(fop.field).String())
builder.WriteRune(']')
builder.WriteRune(' ')
}
fmt.Fprintf(builder, "%s\n", fop.value)
}
return builder.Bytes(), nil
}
if w.verbose && output.Request.Tag != "" {
builder.WriteRune('[')
builder.WriteString(w.aurora.Blue(output.Request.Tag).String())
builder.WriteRune(']')
builder.WriteRune(' ')
}
if output.Request.Method != "" && w.verbose {
builder.WriteRune('[')
builder.WriteString(w.aurora.Green(output.Request.Method).String())
builder.WriteRune(']')
builder.WriteRune(' ')
}
builder.WriteString(output.Request.URL)
if output.Request.Body != "" && w.verbose {
builder.WriteRune(' ')
builder.WriteRune('[')
builder.WriteString(output.Request.Body)
builder.WriteRune(']')
}
if w.verbose {
builder.WriteRune(' ')
builder.WriteRune('[')
builder.WriteString(w.aurora.Yellow("depth:" + strconv.Itoa(output.Request.Depth)).String())
builder.WriteRune(']')
}
return builder.Bytes(), nil
}
================================================
FILE: pkg/output/format_template.go
================================================
package output
import (
"errors"
"fmt"
"io"
"strings"
"github.com/valyala/fasttemplate"
)
func (w *StandardWriter) formatTemplate(output *Result) ([]byte, error) {
var fieldOutputs []fieldOutput
fieldNames := strings.Join(FieldNames, ",")
fieldOutputs = formatField(output, fieldNames)
fieldOutputs = append(fieldOutputs, getValueForCustomField(output)...)
fieldsMap := make(map[string]string)
for _, fo := range fieldOutputs {
fieldsMap[fo.field] = fo.value
}
errUnknownTag := errors.New("unknown tag")
tagFn := fasttemplate.TagFunc(func(w io.Writer, tag string) (int, error) {
value, ok := fieldsMap[tag]
if !ok {
return 0, fmt.Errorf("%w %q", errUnknownTag, tag)
}
return w.Write([]byte(value))
})
out, err := w.outputTemplate.ExecuteFuncStringWithErr(tagFn)
if err != nil {
if errors.Is(err, errUnknownTag) {
// If there is an unknown tag, we just ignore it.
return nil, nil
}
return nil, err
}
return []byte(out), nil
}
================================================
FILE: pkg/output/options.go
================================================
package output
import (
"regexp"
"github.com/projectdiscovery/katana/pkg/utils/extensions"
)
// Options contains the configuration options for output writer
type Options struct {
Colors bool
JSON bool
Verbose bool
StoreResponse bool
NoClobber bool
OmitRaw bool
OmitBody bool
OutputFile string
Fields string
StoreFields string
StoreResponseDir string
StoreFieldDir string
FieldConfig string
ErrorLogFile string
MatchRegex []*regexp.Regexp
FilterRegex []*regexp.Regexp
ExtensionValidator *extensions.Validator
OutputTemplate string
OutputMatchCondition string
OutputFilterCondition string
ExcludeOutputFields []string
FilterPageType []string
}
================================================
FILE: pkg/output/output.go
================================================
package output
import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
jsoniter "github.com/json-iterator/go"
"github.com/logrusorgru/aurora"
"github.com/mitchellh/mapstructure"
"github.com/projectdiscovery/dsl"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/projectdiscovery/katana/pkg/utils/extensions"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
"github.com/stoewer/go-strcase"
"github.com/valyala/fasttemplate"
)
const (
indexFile = "index.txt"
DefaultResponseDir = "katana_response"
)
var (
storeFieldDir = "katana_field"
decolorizerRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)
)
// Writer is an interface which writes output to somewhere for katana events.
type Writer interface {
// Close closes the output writer interface
Close() error
// Write writes the event to file and/or screen.
Write(*Result) error
WriteErr(*Error) error
}
// StandardWriter is an standard output writer structure
type StandardWriter struct {
storeFields []string
fields string
json bool
verbose bool
aurora aurora.Aurora
outputFile *fileWriter
outputMutex *sync.Mutex
storeResponse bool
storeResponseDir string
noClobber bool
omitRaw bool
omitBody bool
errorFile *fileWriter
matchRegex []*regexp.Regexp
filterRegex []*regexp.Regexp
extensionValidator *extensions.Validator
outputTemplate *fasttemplate.Template
outputMatchCondition string
outputFilterCondition string
excludeOutputFields []string
filterPageType []string
}
// New returns a new output writer instance
func New(options Options) (Writer, error) {
writer := &StandardWriter{
fields: options.Fields,
json: options.JSON,
verbose: options.Verbose,
aurora: aurora.NewAurora(options.Colors),
outputMutex: &sync.Mutex{},
storeResponse: options.StoreResponse,
storeResponseDir: options.StoreResponseDir,
noClobber: options.NoClobber,
omitRaw: options.OmitRaw,
omitBody: options.OmitBody,
matchRegex: options.MatchRegex,
filterRegex: options.FilterRegex,
extensionValidator: options.ExtensionValidator,
outputMatchCondition: options.OutputMatchCondition,
outputFilterCondition: options.OutputFilterCondition,
excludeOutputFields: options.ExcludeOutputFields,
filterPageType: options.FilterPageType,
}
if options.StoreFieldDir != "" {
storeFieldDir = options.StoreFieldDir
}
// if fieldConfig empty get the default file
if options.FieldConfig == "" {
var err error
options.FieldConfig, err = initCustomFieldConfigFile()
if err != nil {
return nil, err
}
}
err := parseCustomFieldName(options.FieldConfig)
if err != nil {
return nil, err
}
err = loadCustomFields(options.FieldConfig, fmt.Sprintf("%s,%s", options.Fields, options.StoreFields))
if err != nil {
return nil, err
}
// Perform validations for fields and store-fields
if options.Fields != "" {
if err := validateFieldNames(options.Fields); err != nil {
return nil, errkit.Wrap(err, "output: could not validate fields")
}
}
if options.StoreFields != "" {
_ = os.MkdirAll(storeFieldDir, os.ModePerm)
if err := validateFieldNames(options.StoreFields); err != nil {
return nil, errkit.Wrap(err, "output: could not validate store fields")
}
writer.storeFields = append(writer.storeFields, strings.Split(options.StoreFields, ",")...)
}
if options.OutputFile != "" {
output, err := newFileOutputWriter(options.OutputFile)
if err != nil {
return nil, errkit.Wrap(err, "output: could not create output file")
}
writer.outputFile = output
}
if options.StoreResponse {
writer.storeResponseDir = DefaultResponseDir
if options.StoreResponseDir != DefaultResponseDir && options.StoreResponseDir != "" {
writer.storeResponseDir = options.StoreResponseDir
}
if options.NoClobber {
writer.storeResponseDir = createDirNameNoClobber(writer.storeResponseDir)
_ = os.MkdirAll(writer.storeResponseDir, os.ModePerm)
} else {
removeDirsWithSuffix(writer.storeResponseDir)
_ = os.MkdirAll(writer.storeResponseDir, os.ModePerm)
}
// todo: the index file seems never used?
_, err := newFileOutputWriter(filepath.Join(writer.storeResponseDir, indexFile))
if err != nil {
return nil, errkit.Wrap(err, "output: could not create index file")
}
}
if options.ErrorLogFile != "" {
errorFile, err := newFileOutputWriter(options.ErrorLogFile)
if err != nil {
return nil, errkit.Wrap(err, "output: could not create error file")
}
writer.errorFile = errorFile
}
if options.OutputTemplate != "" {
writer.outputTemplate, err = fasttemplate.NewTemplate(options.OutputTemplate, "{{", "}}")
if err != nil {
return nil, errkit.Wrap(err, "output: could not create output format template")
}
}
return writer, nil
}
// Write writes the result to file and/or screen.
func (w *StandardWriter) Write(result *Result) error {
if result == nil {
return errors.New("result is nil")
}
if len(w.storeFields) > 0 {
storeFields(result, w.storeFields)
}
if !w.extensionValidator.ValidatePath(result.Request.URL) {
return errors.New("result does not match extension filter")
}
if !w.matchOutput(result) {
return errors.New("result does not match output")
}
if w.filterOutput(result) {
return errors.New("result is filtered out")
}
if len(w.filterPageType) > 0 && result.Response != nil && result.Response.KnowledgeBase != nil {
if pageType, ok := result.Response.KnowledgeBase["PageType"].(string); ok {
for _, ft := range w.filterPageType {
if strings.EqualFold(pageType, ft) {
return errors.New("result filtered by page type")
}
}
}
}
var data []byte
var err error
if w.storeResponse && result.HasResponse() {
if fileName, fileWriter, err := getResponseFile(w.storeResponseDir, result.Response.Resp.Request.URL.String()); err == nil {
if absPath, err := filepath.Abs(fileName); err == nil {
fileName = absPath
}
result.Response.StoredResponsePath = fileName
data, err := w.formatResult(result)
if err != nil {
return errkit.Wrap(err, "output: could not store response")
}
if err := updateIndex(w.storeResponseDir, result); err != nil {
return errkit.Wrap(err, "output: could not store response")
}
if err := fileWriter.Write(data); err != nil {
return errkit.Wrap(err, "output: could not store response")
}
_ = fileWriter.Close()
}
}
if w.omitRaw {
result.Request.Raw = ""
if result.Response != nil {
result.Response.Raw = ""
}
}
if w.omitBody && result.HasResponse() {
result.Response.Body = ""
}
var outputKind string
switch {
case w.outputTemplate != nil:
outputKind = "template"
data, err = w.formatTemplate(result)
case w.json:
outputKind = "JSON"
data, err = w.formatJSON(result)
default:
outputKind = "screen"
data, err = w.formatScreen(result)
}
if err != nil {
return errkit.Wrap(err, fmt.Sprintf("output: could not format %s output", outputKind))
}
if len(data) == 0 {
return errors.New("result is empty")
}
w.outputMutex.Lock()
defer w.outputMutex.Unlock()
gologger.Silent().Msgf("%s", string(data))
if w.outputFile != nil {
if !w.json {
data = decolorizerRegex.ReplaceAll(data, []byte(""))
}
if err := w.outputFile.Write(data); err != nil {
return errkit.Wrap(err, "output: could not write to output")
}
}
return nil
}
func (w *StandardWriter) WriteErr(errMessage *Error) error {
data, err := jsoniter.Marshal(errMessage)
if err != nil {
return errkit.Wrap(err, "output: marshal")
}
if len(data) == 0 {
return nil
}
w.outputMutex.Lock()
defer w.outputMutex.Unlock()
if w.errorFile != nil {
if err := w.errorFile.Write(data); err != nil {
return errkit.Wrap(err, "output: write to error file")
}
}
return nil
}
// Close closes the output writer
func (w *StandardWriter) Close() error {
if w.outputFile != nil {
err := w.outputFile.Close()
if err != nil {
return err
}
}
if w.errorFile != nil {
err := w.errorFile.Close()
if err != nil {
return err
}
}
return nil
}
func createDirNameNoClobber(dir string) string {
if !fileutil.FolderExists(dir) {
return dir
}
parentDir, dirName := filepath.Dir(dir), filepath.Base(dir)
entries, err := os.ReadDir(parentDir)
if err != nil {
return dirName
}
highestNum := 0
regex := regexp.MustCompile(fmt.Sprintf("^%s(\\d+)$", regexp.QuoteMeta(dirName)))
for _, entry := range entries {
if entry.IsDir() {
name := entry.Name()
matches := regex.FindStringSubmatch(name)
if matches != nil {
if num, err := strconv.Atoi(matches[1]); err == nil && num > highestNum {
highestNum = num
}
}
}
}
newDirName := fmt.Sprintf("%s%d", dirName, highestNum+1)
newFullPath := filepath.Join(parentDir, newDirName)
return newFullPath
}
func removeDirsWithSuffix(dir string) {
parentDir, dirName := filepath.Dir(dir), filepath.Base(dir)
entries, _ := os.ReadDir(parentDir)
pattern := fmt.Sprintf("^%s(\\d*)$", regexp.QuoteMeta(dirName))
regex := regexp.MustCompile(pattern)
for _, entry := range entries {
if entry.IsDir() {
name := entry.Name()
if regex.MatchString(name) {
fullPath := filepath.Join(parentDir, name)
_ = os.RemoveAll(fullPath)
}
}
}
}
// matchOutput checks if the event matches the output regex
func (w *StandardWriter) matchOutput(event *Result) bool {
if w.matchRegex == nil && w.outputMatchCondition == "" {
return true
}
for _, regex := range w.matchRegex {
if regex.MatchString(event.Request.URL) {
return true
}
}
if w.outputMatchCondition != "" {
return evalDslExpr(event, w.outputMatchCondition)
}
return false
}
// filterOutput returns true if the event should be filtered out
func (w *StandardWriter) filterOutput(event *Result) bool {
if w.filterRegex == nil && w.outputFilterCondition == "" {
return false
}
for _, regex := range w.filterRegex {
if regex.MatchString(event.Request.URL) {
return true
}
}
if w.outputFilterCondition != "" {
return evalDslExpr(event, w.outputFilterCondition)
}
return false
}
func evalDslExpr(result *Result, dslExpr string) bool {
resultMap, err := resultToMap(*result)
if err != nil {
gologger.Warning().Msgf("Could not map result: %s\n", err)
return false
}
res, err := dsl.EvalExpr(dslExpr, resultMap)
if err != nil && !ignoreErr(err) {
gologger.Error().Msgf("Could not evaluate DSL expression: %s\n", err)
return false
}
return res == true
}
func resultToMap(result Result) (map[string]interface{}, error) {
resultMap := make(map[string]any)
config := &mapstructure.DecoderConfig{
TagName: "json",
Result: &resultMap,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return nil, fmt.Errorf("error creating decoder: %v", err)
}
err = decoder.Decode(result)
if err != nil {
return nil, fmt.Errorf("error decoding: %v", err)
}
requestMap := make(map[string]any)
if err := mapstructure.Decode(result.Request, &requestMap); err == nil {
for k, v := range requestMap {
resultMap[strcase.SnakeCase(k)] = v
}
}
responseMap := make(map[string]any)
if err := mapstructure.Decode(result.Response, &responseMap); err == nil {
for k, v := range responseMap {
if strings.ToLower(k) == "headers" {
if headers, ok := v.(navigation.Headers); ok {
for hk, hv := range headers {
resultMap[strcase.SnakeCase(hk)] = hv
}
}
} else {
resultMap[strcase.SnakeCase(k)] = v
}
}
}
return flatten(resultMap), nil
}
// mapsutil.Flatten w/o separator
func flatten(m map[string]any) map[string]any {
o := make(map[string]any)
for k, v := range m {
switch child := v.(type) {
case map[string]any:
nm := flatten(child)
for nk, nv := range nm {
o[strcase.SnakeCase(nk)] = nv
}
default:
o[strcase.SnakeCase(k)] = v
}
}
return o
}
var (
// showDSLErr controls whether to show hidden DSL errors or not
showDSLErr = strings.EqualFold(os.Getenv("SHOW_DSL_ERRORS"), "true")
)
// ignoreErr checks if the error is to be ignored or not
// Reference: https://github.com/projectdiscovery/katana/pull/537
func ignoreErr(err error) bool {
if showDSLErr {
return false
}
if errors.Is(err, dsl.ErrParsingArg) || strings.Contains(err.Error(), "No parameter") {
return true
}
return false
}
================================================
FILE: pkg/output/responses.go
================================================
package output
import (
"bytes"
"crypto/sha1"
"encoding/hex"
"os"
"path/filepath"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/utils/errkit"
urlutil "github.com/projectdiscovery/utils/url"
)
func getResponseHash(URL string) string {
hash := sha1.Sum([]byte(URL))
return hex.EncodeToString(hash[:])
}
func (w *StandardWriter) formatResult(result *Result) ([]byte, error) {
builder := &bytes.Buffer{}
builder.WriteString(result.Request.URL)
builder.WriteString("\n\n\n")
builder.WriteString(result.Request.Raw)
builder.WriteString("\n\n")
builder.WriteString(result.Response.Raw)
return builder.Bytes(), nil
}
func getResponseHost(URL string) (string, error) {
u, err := urlutil.Parse(URL)
if err != nil {
return "", err
}
return filepath.Clean(strings.ReplaceAll(u.Host, ":", "_")), nil
}
func createHostDir(storeResponseFolder, domain string) string {
_ = os.MkdirAll(filepath.Join(storeResponseFolder, domain), os.ModePerm)
return filepath.Join(storeResponseFolder, domain)
}
func getResponseFile(storeResponseFolder, URL string) (string, *fileWriter, error) {
domain, err := getResponseHost(URL)
if err != nil {
return "", nil, err
}
fileName := getResponseFileName(storeResponseFolder, domain, URL)
output, err := newFileOutputWriter(fileName)
if err != nil {
return "", nil, errkit.Wrap(err, "output: could not create output file")
}
return fileName, output, nil
}
func getResponseFileName(storeResponseFolder, domain, URL string) string {
folder := createHostDir(storeResponseFolder, domain)
file := getResponseHash(URL) + ".txt"
return filepath.Join(folder, file)
}
func updateIndex(storeResponseFolder string, result *Result) error {
index, err := os.OpenFile(filepath.Join(storeResponseFolder, indexFile), os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer func() {
if err := index.Close(); err != nil {
gologger.Error().Msgf("Error closing index: %v\n", err)
}
}()
builder := &bytes.Buffer{}
domain, err := getResponseHost(result.Request.URL)
if err != nil {
return err
}
builder.WriteString(getResponseFileName(storeResponseFolder, domain, result.Request.URL))
builder.WriteRune(' ')
builder.WriteString(result.Request.URL)
builder.WriteRune(' ')
builder.WriteString("(" + result.Response.Resp.Status + ")")
builder.WriteRune('\n')
if _, writeErr := index.Write(builder.Bytes()); writeErr != nil {
return errkit.Wrap(err, "output: could not update index")
}
return nil
}
================================================
FILE: pkg/output/result.go
================================================
package output
import (
"time"
"github.com/projectdiscovery/katana/pkg/navigation"
)
// Result of the crawling
type Result struct {
Timestamp time.Time `json:"timestamp,omitempty"`
Request *navigation.Request `json:"request,omitempty"`
Response *navigation.Response `json:"response,omitempty"`
Error string `json:"error,omitempty"`
}
// HasResponse checks if the result has a valid response
func (r *Result) HasResponse() bool {
return r.Response != nil && r.Response.Resp != nil
}
================================================
FILE: pkg/types/crawler_options.go
================================================
package types
import (
"context"
"log/slog"
"os/user"
"regexp"
"time"
"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/katana/pkg/engine/parser"
"github.com/projectdiscovery/katana/pkg/output"
"github.com/projectdiscovery/katana/pkg/utils/extensions"
"github.com/projectdiscovery/katana/pkg/utils/filters"
"github.com/projectdiscovery/katana/pkg/utils/scope"
"github.com/projectdiscovery/ratelimit"
"github.com/happyhackingspace/dit"
"github.com/projectdiscovery/utils/errkit"
urlutil "github.com/projectdiscovery/utils/url"
wappalyzer "github.com/projectdiscovery/wappalyzergo"
)
// CrawlerOptions contains helper utilities for the crawler
type CrawlerOptions struct {
// OutputWriter is the interface for writing output
OutputWriter output.Writer
// RateLimit is a mechanism for controlling request rate limit
RateLimit *ratelimit.Limiter
// Parser is a mechanism for extracting new URLS from responses
Parser *parser.Parser
// Options contains the user specified configuration options
Options *Options
// ExtensionsValidator is a validator for file extensions
ExtensionsValidator *extensions.Validator
// UniqueFilter is a filter for deduplication of unique items
UniqueFilter filters.Filter
// ScopeManager is a manager for validating crawling scope
ScopeManager *scope.Manager
// Dialer is instance of the dialer for global crawler
Dialer *fastdialer.Dialer
// Wappalyzer instance for technologies detection
Wappalyzer *wappalyzer.Wappalyze
// DitClassifier instance for knowledge base classification
DitClassifier *dit.Classifier
// Optional structured logger for headless crawler
Logger *slog.Logger
// ChromeUser is the user to use for chrome
ChromeUser *user.User
}
// NewCrawlerOptions creates a new crawler options structure
// from user specified options.
func NewCrawlerOptions(options *Options) (*CrawlerOptions, error) {
options.ConfigureOutput()
extensionsValidator := extensions.NewValidator(options.ExtensionsMatch, options.ExtensionFilter, options.NoDefaultExtFilter)
parserOptions := &parser.Options{
AutomaticFormFill: options.AutomaticFormFill,
ScrapeJSLuiceResponses: options.ScrapeJSLuiceResponses,
ScrapeJSResponses: options.ScrapeJSResponses,
DisableRedirects: options.DisableRedirects,
}
responseParser := parser.NewResponseParser()
responseParser.InitWithOptions(parserOptions)
dialerOpts := fastdialer.DefaultOptions
if len(options.Resolvers) > 0 {
dialerOpts.BaseResolvers = options.Resolvers
}
fastdialerInstance, err := fastdialer.NewDialer(dialerOpts)
if err != nil {
return nil, err
}
scopeManager, err := scope.NewManager(options.Scope, options.OutOfScope, options.FieldScope, options.NoScope)
if err != nil {
return nil, errkit.Wrap(err, "could not create scope manager")
}
itemFilter, err := filters.NewSimple()
if err != nil {
return nil, errkit.Wrap(err, "could not create filter")
}
outputOptions := output.Options{
Colors: !options.NoColors,
JSON: options.JSON,
Verbose: options.Verbose,
StoreResponse: options.StoreResponse,
OutputFile: options.OutputFile,
Fields: options.Fields,
StoreFields: options.StoreFields,
StoreResponseDir: options.StoreResponseDir,
NoClobber: options.NoClobber,
StoreFieldDir: options.StoreFieldDir,
OmitRaw: options.OmitRaw,
OmitBody: options.OmitBody,
FieldConfig: options.FieldConfig,
ErrorLogFile: options.ErrorLogFile,
MatchRegex: options.MatchRegex,
FilterRegex: options.FilterRegex,
ExtensionValidator: extensionsValidator,
OutputTemplate: options.OutputTemplate,
OutputMatchCondition: options.OutputMatchCondition,
OutputFilterCondition: options.OutputFilterCondition,
ExcludeOutputFields: options.ExcludeOutputFields,
FilterPageType: options.FilterPageType,
}
for _, mr := range options.OutputMatchRegex {
cr, err := regexp.Compile(mr)
if err != nil {
return nil, errkit.Wrap(err, "Invalid value for match regex option")
}
outputOptions.MatchRegex = append(outputOptions.MatchRegex, cr)
}
for _, fr := range options.OutputFilterRegex {
cr, err := regexp.Compile(fr)
if err != nil {
return nil, errkit.Wrap(err, "Invalid value for filter regex option")
}
outputOptions.FilterRegex = append(outputOptions.FilterRegex, cr)
}
outputWriter, err := output.New(outputOptions)
if err != nil {
return nil, errkit.Wrap(err, "could not create output writer")
}
crawlerOptions := &CrawlerOptions{
ExtensionsValidator: extensionsValidator,
Parser: responseParser,
ScopeManager: scopeManager,
UniqueFilter: itemFilter,
Options: options,
Dialer: fastdialerInstance,
OutputWriter: outputWriter,
}
if options.RateLimit > 0 {
crawlerOptions.RateLimit = ratelimit.New(context.Background(), uint(options.RateLimit), time.Second)
} else if options.RateLimitMinute > 0 {
crawlerOptions.RateLimit = ratelimit.New(context.Background(), uint(options.RateLimitMinute), time.Minute)
}
if options.TechDetect {
wappalyze, err := wappalyzer.New()
if err != nil {
return nil, err
}
crawlerOptions.Wappalyzer = wappalyze
}
if len(options.FilterPageType) > 0 {
options.KnowledgeBase = true
}
if options.KnowledgeBase {
classifier, err := dit.New()
if err != nil {
return nil, errkit.Wrap(err, "could not init dit classifier")
}
crawlerOptions.DitClassifier = classifier
}
if options.MaxOnclickLinks <= 0 {
options.MaxOnclickLinks = 10
}
return crawlerOptions, nil
}
// Close closes the crawler options resources
func (c *CrawlerOptions) Close() error {
c.UniqueFilter.Close()
return c.OutputWriter.Close()
}
func (c *CrawlerOptions) ValidatePath(path string) bool {
if c.ExtensionsValidator != nil {
return c.ExtensionsValidator.ValidatePath(path)
}
return true
}
// ClassifyPage classifies a page using the dit classifier and returns the knowledge base map.
func (c *CrawlerOptions) ClassifyPage(body string) map[string]any {
if c.DitClassifier == nil {
return nil
}
result, err := c.DitClassifier.ExtractPageType(body)
if err != nil {
return nil
}
kb := map[string]any{
"PageType": result.Type,
}
if len(result.Forms) > 0 {
kb["Forms"] = result.Forms
}
return kb
}
// ValidateScope validates scope for an AbsURL
func (c *CrawlerOptions) ValidateScope(absURL, rootHostname string) (bool, error) {
parsed, err := urlutil.Parse(absURL)
if err != nil {
return false, err
}
if c.ScopeManager != nil {
return c.ScopeManager.Validate(parsed.URL, rootHostname)
}
return true, nil
}
================================================
FILE: pkg/types/default.go
================================================
package types
import "github.com/projectdiscovery/katana/pkg/utils/queue"
var DefaultOptions Options
func init() {
DefaultOptions = Options{
MaxDepth: 3,
BodyReadSize: 4 * 1024 * 1024, // 4MB
Timeout: 10,
TimeStable: 1,
Retries: 1,
Strategy: queue.DepthFirst.String(),
FieldScope: "rdn",
Concurrency: 10,
Parallelism: 10,
RateLimit: 150,
}
}
================================================
FILE: pkg/types/options.go
================================================
package types
import (
"regexp"
"strings"
"time"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/katana/pkg/output"
fileutil "github.com/projectdiscovery/utils/file"
logutil "github.com/projectdiscovery/utils/log"
)
// OnResultCallback (output.Result)
type OnResultCallback func(output.Result)
// OnSkipURLCallback (string)
type OnSkipURLCallback func(string)
type Options struct {
// URLs contains a list of URLs for crawling
URLs goflags.StringSlice
// Resume the scan from the state stored in the resume config file
Resume string
// Exclude host matching specified filter ('cdn', 'private-ips', cidr, ip, regex)
Exclude goflags.StringSlice
// Scope contains a list of regexes for in-scope URLS
Scope goflags.StringSlice
// OutOfScope contains a list of regexes for out-scope URLS
OutOfScope goflags.StringSlice
// NoScope disables host based default scope
NoScope bool
// DisplayOutScope displays out of scope items in results
DisplayOutScope bool
// ExtensionsMatch contains extensions to match explicitly
ExtensionsMatch goflags.StringSlice
// ExtensionFilter contains additional items for filter list
ExtensionFilter goflags.StringSlice
// NoDefaultExtFilter removes the default extensions from the filter list
NoDefaultExtFilter bool
// OutputMatchCondition is the condition to match output
OutputMatchCondition string
// OutputFilterCondition is the condition to filter output
OutputFilterCondition string
// MaxDepth is the maximum depth to crawl
MaxDepth int
// BodyReadSize is the maximum size of response body to read
BodyReadSize int
// Timeout is the time to wait for request in seconds
Timeout int
// TimeStable is the time to wait until the page is stable
TimeStable int
// CrawlDuration is the duration in seconds to crawl target from
CrawlDuration time.Duration
// MaxFailureCount is the maximum number of consecutive failures before stopping
MaxFailureCount int
// Delay is the delay between each crawl requests in seconds
Delay int
// RateLimit is the maximum number of requests to send per second
RateLimit int
// Retries is the number of retries to do for request
Retries int
// RateLimitMinute is the maximum number of requests to send per minute
RateLimitMinute int
// Concurrency is the number of concurrent crawling goroutines
Concurrency int
// Parallelism is the number of urls processing goroutines
Parallelism int
// FormConfig is the path to the form configuration file
FormConfig string
// Proxy is the URL for the proxy server
Proxy string
// Strategy is the crawling strategy. depth-first or breadth-first
Strategy string
// FieldScope is the scope field for default DNS scope
FieldScope string
// OutputFile is the file to write output to
OutputFile string
// KnownFiles enables crawling of knows files like robots.txt, sitemap.xml, etc
KnownFiles string
// Fields is the fields to format in output
Fields string
// StoreFields is the fields to store in separate per-host files
StoreFields string
// FieldConfig is the path to the custom field configuration file
FieldConfig string
// NoColors disables coloring of response output
NoColors bool
// JSON enables writing output in JSON format
JSON bool
// ExcludeOutputFields is the list of fields to exclude from the output
ExcludeOutputFields goflags.StringSlice
// ListOutputFields is the list of fields
ListOutputFields bool
// Silent shows only output
Silent bool
// Verbose specifies showing verbose output
Verbose bool
// TechDetect enables technology detection
TechDetect bool
// EnableDiagnostics enables diagnostics
EnableDiagnostics bool
// Version enables showing of crawler version
Version bool
// ScrapeJSResponses enables scraping of relative endpoints from javascript
ScrapeJSResponses bool
// ScrapeJSLuiceResponses enables scraping of endpoints from javascript using jsluice
ScrapeJSLuiceResponses bool
// CustomHeaders is a list of custom headers to add to request
CustomHeaders goflags.StringSlice
// Headless enables headless scraping
Headless bool
// HeadlessHybrid enables headless hybrid scraping
HeadlessHybrid bool
// AutomaticFormFill enables optional automatic form filling and submission
AutomaticFormFill bool
// FormExtraction enables extraction of form, input, textarea & select elements
FormExtraction bool
// UseInstalledChrome skips chrome install and use local instance
UseInstalledChrome bool
// ShowBrowser specifies whether the show the browser in headless mode
ShowBrowser bool
// HeadlessOptionalArguments specifies optional arguments to pass to Chrome
HeadlessOptionalArguments goflags.StringSlice
// HeadlessNoSandbox specifies if chrome should be start in --no-sandbox mode
HeadlessNoSandbox bool
// SystemChromePath : Specify the chrome binary path for headless crawling
SystemChromePath string
// ChromeWSUrl : Specify the Chrome debugger websocket url for a running Chrome instance to attach to
ChromeWSUrl string
// OnResult allows callback function on a result
OnResult OnResultCallback
// OnSkipURL allows callback function on a skipped url
OnSkipURL OnSkipURLCallback
// StoreResponse specifies if katana should store http requests/responses
StoreResponse bool
// StoreResponseDir specifies if katana should use a custom directory to store http requests/responses
StoreResponseDir string
// NoClobber specifies if katana should overwrite existing output files
NoClobber bool
// StoreFieldDir specifies if katana should use a custom directory to store fields
StoreFieldDir string
// OmitRaw omits raw requests/responses from the output
OmitRaw bool
// OmitBody omits the response body from the output
OmitBody bool
// ChromeDataDir : Specify the --user-data-dir to chrome binary to preserve sessions
ChromeDataDir string
// HeadlessNoIncognito specifies if chrome should be started without incognito mode
HeadlessNoIncognito bool
// XhrExtraction extract xhr requests
XhrExtraction bool
// HealthCheck determines if a self-healthcheck should be performed
HealthCheck bool
// PprofServer enables pprof server
PprofServer bool
// ErrorLogFile specifies a file to write with the errors of all requests
ErrorLogFile string
// Resolvers contains custom resolvers
Resolvers goflags.StringSlice
// OutputTemplate enables custom output template
OutputTemplate string
// OutputMatchRegex is the regex to match output url
OutputMatchRegex goflags.StringSlice
// OutputFilterRegex is the regex to filter output url
OutputFilterRegex goflags.StringSlice
// FilterRegex is the slice regex to filter url
FilterRegex []*regexp.Regexp
// MatchRegex is the slice regex to match url
MatchRegex []*regexp.Regexp
//DisableUpdateCheck disables automatic update check
DisableUpdateCheck bool
//IgnoreQueryParams ignore crawling same path with different query-param values
IgnoreQueryParams bool
// FilterSimilar filters crawling of similar looking URLs
// by normalizing variable path segments (IDs, UUIDs, hashes, dates)
FilterSimilar bool
// FilterSimilarThreshold is the number of distinct values at a path position
// before it is treated as a parameter (default 10, lower = more aggressive)
FilterSimilarThreshold int
// Debug
Debug bool
// TlsImpersonate enables experimental tls ClientHello randomization for standard crawler
TlsImpersonate bool
// DisableRedirects disables the following of redirects
DisableRedirects bool
// PathClimb enables path expansion (auto crawl discovered paths)
PathClimb bool
// DisableUniqueFilter disables duplicate content filtering
DisableUniqueFilter bool
// MaxOnclickLinks is the maximum number of onclick links to process per page (default: 10)
MaxOnclickLinks int
CaptchaSolverProvider string
CaptchaSolverAPIKey string
// KnowledgeBase enables knowledge base classification using dit
KnowledgeBase bool
// FilterPageType filters results by page type
FilterPageType goflags.StringSlice
}
func (options *Options) ParseCustomHeaders() map[string]string {
customHeaders := make(map[string]string)
for _, v := range options.CustomHeaders {
if headerParts := strings.SplitN(v, ":", 2); len(headerParts) >= 2 {
customHeaders[strings.Trim(headerParts[0], " ")] = strings.Trim(headerParts[1], " ")
}
}
return customHeaders
}
func (options *Options) ParseHeadlessOptionalArguments() map[string]string {
var (
lastKey string
optionalArguments = make(map[string]string)
)
for _, v := range options.HeadlessOptionalArguments {
if v == "" {
continue
}
if argParts := strings.SplitN(v, "=", 2); len(argParts) >= 2 {
key := strings.TrimSpace(argParts[0])
value := strings.TrimSpace(argParts[1])
if key != "" && value != "" {
optionalArguments[key] = value
lastKey = key
}
} else if !strings.HasPrefix(v, "--") {
optionalArguments[lastKey] += "," + v
} else {
optionalArguments[v] = ""
}
}
return optionalArguments
}
func (options *Options) ShouldResume() bool {
return options.Resume != "" && fileutil.FileExists(options.Resume)
}
// ConfigureOutput configures the output logging levels to be displayed on the screen
func (options *Options) ConfigureOutput() {
if options.Silent {
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
} else if options.Verbose {
gologger.DefaultLogger.SetMaxLevel(levels.LevelWarning)
} else if options.Debug {
gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)
} else {
gologger.DefaultLogger.SetMaxLevel(levels.LevelInfo)
}
logutil.DisableDefaultLogger()
}
================================================
FILE: pkg/types/options_test.go
================================================
package types
import (
"strings"
"testing"
"github.com/projectdiscovery/goflags"
"github.com/stretchr/testify/require"
)
func TestParseCustomHeaders(t *testing.T) {
tests := []struct {
name string
input string
want map[string]string
}{
{
name: "single value",
input: "a:b",
want: map[string]string{"a": "b"},
},
{
name: "empty string",
input: "",
want: map[string]string{},
},
{
name: "empty value",
input: "a:",
want: map[string]string{"a": ""},
},
{
name: "double input",
input: "a:b,c:d",
want: map[string]string{"a": "b", "c": "d"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
strsl := goflags.StringSlice{}
for _, v := range strings.Split(tt.input, ",") {
//nolint
strsl.Set(v)
}
opt := Options{CustomHeaders: strsl}
got := opt.ParseCustomHeaders()
require.Equal(t, tt.want, got)
})
}
}
func TestParseHeadlessOptionalArguments(t *testing.T) {
tests := []struct {
name string
input string
want map[string]string
}{
{
name: "single value",
input: "a=b",
want: map[string]string{"a": "b"},
},
{
name: "empty string",
input: "",
want: map[string]string{},
},
{
name: "empty key",
input: "=b",
want: map[string]string{},
},
{
name: "empty value",
input: "a=",
want: map[string]string{},
},
{
name: "double input",
input: "a=b,c=d",
want: map[string]string{"a": "b", "c": "d"},
},
{
name: "duplicated input",
input: "a=b,a=b",
want: map[string]string{"a": "b"},
},
{
name: "values with dash with boolean flag at the end",
input: "--a=a/b,c/d--z--n--m/a,--c=k,--h",
want: map[string]string{"--a": "a/b,c/d--z--n--m/a", "--c": "k", "--h": ""},
},
{
name: "values with dash boolean flag at the beginning",
input: "--h,--a=a/b,c/d--z--n--m/a,--c=k",
want: map[string]string{"--h": "", "--a": "a/b,c/d--z--n--m/a", "--c": "k"},
},
{
name: "values with dash boolean flag in the middle",
input: "--a=a/b,c/d--z--n--m/a,--h,--c=k",
want: map[string]string{"--a": "a/b,c/d--z--n--m/a", "--h": "", "--c": "k"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
strsl := goflags.StringSlice{}
for _, v := range strings.Split(tt.input, ",") {
//nolint
strsl.Set(v)
}
opt := Options{HeadlessOptionalArguments: strsl}
got := opt.ParseHeadlessOptionalArguments()
require.Equal(t, tt.want, got)
})
}
}
================================================
FILE: pkg/utils/extensions/extensions.go
================================================
package extensions
import (
"path"
"strings"
"github.com/projectdiscovery/gologger"
urlutil "github.com/projectdiscovery/utils/url"
)
// defaultDenylist is the default list of extensions to be denied
var defaultExtFilter = []string{".3g2", ".3gp", ".7z", ".apk", ".arj", ".avi", ".axd", ".bmp", ".csv", ".deb", ".dll", ".doc", ".drv", ".eot", ".exe", ".flv", ".gif", ".gifv", ".gz", ".h264", ".ico", ".iso", ".jar", ".jpeg", ".jpg", ".lock", ".m4a", ".m4v", ".map", ".mkv", ".mov", ".mp3", ".mp4", ".mpeg", ".mpg", ".msi", ".ogg", ".ogm", ".ogv", ".otf", ".pdf", ".pkg", ".png", ".ppt", ".psd", ".rar", ".rm", ".rpm", ".svg", ".swf", ".sys", ".tar.gz", ".tar", ".tif", ".tiff", ".ttf", ".txt", ".vob", ".wav", ".webm", ".webp", ".wmv", ".woff", ".woff2", ".xcf", ".xls", ".xlsx", ".zip"}
// Validator is a validator for file extension
type Validator struct {
extensionsMatch map[string]struct{}
extensionsFilter map[string]struct{}
}
// NewValidator creates a new extension validator instance
func NewValidator(extensionsMatch, extensionsFilter []string, noDefaultExtFilter bool) *Validator {
validator := &Validator{
extensionsMatch: make(map[string]struct{}),
extensionsFilter: make(map[string]struct{}),
}
for _, extension := range extensionsMatch {
validator.extensionsMatch[normalizeExtension(extension)] = struct{}{}
}
if !noDefaultExtFilter {
for _, item := range defaultExtFilter {
validator.extensionsFilter[normalizeExtension(item)] = struct{}{}
}
}
for _, extension := range extensionsFilter {
validator.extensionsFilter[normalizeExtension(extension)] = struct{}{}
}
return validator
}
// ValidatePath returns true if an extension is allowed by the validator
func (e *Validator) ValidatePath(item string) bool {
var extension string
u, err := urlutil.Parse(item)
if err != nil {
gologger.Warning().Msgf("validatepath: failed to parse url %v got %v", item, err)
return false
}
if u.Path != "" {
extension = strings.ToLower(path.Ext(u.Path))
} else {
extension = strings.ToLower(path.Ext(item))
}
if extension == "" && len(e.extensionsMatch) > 0 {
return false
}
if len(e.extensionsMatch) > 0 {
if _, ok := e.extensionsMatch[extension]; ok {
return true
}
return false
}
if _, ok := e.extensionsFilter[extension]; ok {
return false
}
return true
}
func normalizeExtension(extension string) string {
extension = strings.ToLower(extension)
if strings.HasPrefix(extension, ".") {
return extension
}
return "." + extension
}
================================================
FILE: pkg/utils/extensions/extensions_test.go
================================================
package extensions
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestValidatorValidate(t *testing.T) {
validator := NewValidator([]string{".go"}, nil, false)
require.True(t, validator.ValidatePath("main.go"), "could not validate correct data with extensions")
require.False(t, validator.ValidatePath("main.php"), "could not validate correct data with wrong extension")
validator = NewValidator(nil, []string{".php"}, false)
require.False(t, validator.ValidatePath("main.php"), "could not validate correct data with deny list extension")
require.True(t, validator.ValidatePath("main.go"), "could not validate correct data with no custom extensions")
validator = NewValidator([]string{"png"}, nil, false)
require.True(t, validator.ValidatePath("main.png"), "could not validate correct data with default denylist bypass")
validator = NewValidator(nil, nil, true)
require.True(t, validator.ValidatePath("main.png"), "could not validate correct data with no default extension filter")
validator = NewValidator(nil, []string{"png"}, true)
require.False(t, validator.ValidatePath("main.png"), "could not validate correct data with no default extension filter and custom filter")
}
================================================
FILE: pkg/utils/filters/filters.go
================================================
package filters
// Filter is an interface implemented by deduplication mechanism
type Filter interface {
// Close closes the filter and releases associated resources
Close()
// UniqueURL specifies whether a URL is unique
UniqueURL(url string) bool
// UniqueContent specifies whether a content is unique
// Deduplication is done by hashing of the response data.
//
// TODO: Consider levenshtein length / keyword based hashing
// to account for dynamic response content.
UniqueContent(content []byte) bool
// IsCycle attempts to detect if the current URL is a cycle
// until graph navigation is implemented, the only ways to discard a potential
// loop cycle are
// - implementing upper hard limit to the URL length (https://bugs.chromium.org/p/chromium/issues/detail?id=69227 => 2Mb)
// - Heuristically find the longest repeating substring and set a max threshold of how many max times it should repeat (eg. 10)
// Todo: This should be replace with graph cycle detection => https://github.com/projectdiscovery/katana/pull/174
IsCycle(url string) bool
}
================================================
FILE: pkg/utils/filters/filters_test.go
================================================
package filters
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSimpleFilter(t *testing.T) {
simple, err := NewSimple()
require.NoError(t, err, "could not create filter")
defer simple.Close()
unique := simple.UniqueURL("https://example.com")
require.True(t, unique, "could not get unique value")
unique = simple.UniqueURL("https://example.com")
require.False(t, unique, "could get unique value")
}
================================================
FILE: pkg/utils/filters/simple.go
================================================
package filters
import (
"crypto/md5"
"encoding/hex"
"github.com/projectdiscovery/hmap/store/hybrid"
stringsutil "github.com/projectdiscovery/utils/strings"
)
const (
// TODO: this should be lowered to a reasonable amount (eg: 1024-2048-4096)
MaxChromeURLLength = 2097152
// TODO: fine tune the number
MinSequenceLength = 10
MaxSequenceCount = 10
)
// Simple is a simple unique URL filter.
//
// The URLs are maintained in a global sync.Map for
// deduplication and no normalization is performed.
type Simple struct {
data *hybrid.HybridMap
}
// NewSimple returns a new simple filter
func NewSimple() (*Simple, error) {
hmap, err := hybrid.New(hybrid.DefaultDiskOptions)
if err != nil {
return nil, err
}
return &Simple{data: hmap}, nil
}
// UniqueURL returns true if the URL is unique
func (s *Simple) UniqueURL(url string) bool {
_, found := s.data.Get(url)
if found {
return false
}
_ = s.data.Set(url, nil)
return true
}
// UniqueContent returns true if the content is unique
func (s *Simple) UniqueContent(data []byte) bool {
hash := md5.Sum([]byte(data))
encoded := hex.EncodeToString(hash[:])
_, found := s.data.Get(encoded)
if found {
return false
}
_ = s.data.Set(encoded, nil)
return true
}
// Close closes the filter and releases associated resources
func (s *Simple) Close() {
_ = s.data.Close()
}
// IsCycle attempts to determine if the url is a cycle loop
func (s *Simple) IsCycle(url string) bool {
if len(url) > MaxChromeURLLength {
return true
}
if sequence := stringsutil.LongestRepeatingSequence(url); sequence.Count >= MaxSequenceCount && len(sequence.Sequence) > MinSequenceLength {
return true
}
return false
}
================================================
FILE: pkg/utils/formfields.go
================================================
package utils
import (
"strings"
"github.com/projectdiscovery/katana/pkg/navigation"
"github.com/PuerkitoBio/goquery"
"github.com/projectdiscovery/utils/generic"
urlutil "github.com/projectdiscovery/utils/url"
)
// parses form, input, textarea & select elements
func ParseFormFields(document *goquery.Document) []navigation.Form {
var forms []navigation.Form
document.Find("form").Each(func(i int, formElem *goquery.Selection) {
form := navigation.Form{}
action, _ := formElem.Attr("action")
method, _ := formElem.Attr("method")
enctype, _ := formElem.Attr("enctype")
if method == "" {
method = "GET"
}
if enctype == "" && method != "GET" {
enctype = "application/x-www-form-urlencoded"
}
if actionUrl, err := urlutil.ParseURL(action, true); err == nil {
// do not modify absolute urls and windows paths
if actionUrl.IsAbs() || strings.HasPrefix(action, "//") || strings.HasPrefix(action, "\\") {
// keep absolute urls as is
_ = action
} else if document.Url != nil {
// concatenate relative urls with base url
// clone base url
cloned, err := urlutil.ParseURL(document.Url.String(), true)
if err != nil {
return
}
if strings.HasPrefix(action, "/") {
// relative path
// => https://example.com/root_rel
_ = cloned.UpdateRelPath(action, true)
action = cloned.String()
} else {
// => https://example.com/path/path_rel
if err := cloned.MergePath(action, false); err != nil {
return
}
action = cloned.String()
}
}
} else {
action = document.Url.String()
}
form.Method = strings.ToUpper(method)
form.Action = action
form.Enctype = enctype
formElem.Find("input, textarea, select").Each(func(i int, inputElem *goquery.Selection) {
name, ok := inputElem.Attr("name")
if !ok {
return
}
form.Parameters = append(form.Parameters, name)
})
if !generic.EqualsAll("", form.Action, form.Method, form.Enctype) || len(form.Parameters) > 0 {
forms = append(forms, form)
}
})
return forms
}
================================================
FILE: pkg/utils/formfields_test.go
================================================
package utils
import (
"net/url"
"strings"
"testing"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/require"
)
var htmlFormExample = `
HTML Form Test
`
func TestParseFormFields(t *testing.T) {
document, err := goquery.NewDocumentFromReader(strings.NewReader(htmlFormExample))
require.NoError(t, err, "could not read document")
document.Url, _ = url.Parse("https://example.com/path")
forms := ParseFormFields(document)
require.Equal(t, "https://example.com/test", forms[0].Action)
require.Equal(t, "POST", forms[0].Method)
require.Equal(t, "POST", forms[1].Method)
require.Equal(t, "https://abs.example.com", forms[1].Action)
require.Equal(t, "GET", forms[2].Method)
require.Equal(t, "//prel.example.com", forms[2].Action)
require.Equal(t, "GET", forms[3].Method)
require.Equal(t, "\\\\unc.example.com", forms[3].Action)
require.Equal(t, "GET", forms[4].Method)
require.Equal(t, "https://example.com/root_rel", forms[4].Action)
require.Equal(t, "GET", forms[5].Method)
require.Equal(t, "https://example.com/path/rel_path", forms[5].Action)
require.Equal(t, "GET", forms[6].Method)
require.Equal(t, "https://example.com/path", forms[6].Action)
require.Contains(t, forms[0].Parameters, "firstname")
require.Contains(t, forms[0].Parameters, "textarea1")
require.Contains(t, forms[0].Parameters, "select1")
require.Equal(t, 3, len(forms[0].Parameters), "found more or less parameters than where present")
require.Equal(t, 7, len(forms), "found more or less forms than where present")
}
================================================
FILE: pkg/utils/formfill.go
================================================
package utils
import (
"fmt"
"strconv"
"github.com/PuerkitoBio/goquery"
mapsutil "github.com/projectdiscovery/utils/maps"
"github.com/rs/xid"
)
// FormData is the global form fill data instance
var FormData FormFillData
func init() {
FormData = DefaultFormFillData
}
// FormFillData contains suggestions for form filling
type FormFillData struct {
Email string `yaml:"email"`
Color string `yaml:"color"`
Password string `yaml:"password"`
PhoneNumber string `yaml:"phone"`
Placeholder string `yaml:"placeholder"`
}
var DefaultFormFillData = FormFillData{
Email: fmt.Sprintf("%s@example.org", xid.New().String()),
Color: "#e66465",
Password: "katanaP@assw0rd1",
PhoneNumber: "2124567890",
Placeholder: "katana",
}
// FormInput is an input for a form field
type FormInput struct {
Type string
Name string
Value string
Attributes mapsutil.OrderedMap[string, string]
}
// SelectOption is an option for a select input
type SelectOption struct {
Value string
Selected string
Attributes mapsutil.OrderedMap[string, string]
}
// FormSelect is a select input for a form field
type FormSelect struct {
Name string
Attributes mapsutil.OrderedMap[string, string]
SelectOptions []SelectOption
}
type FormTextArea struct {
Name string
Attributes mapsutil.OrderedMap[string, string]
}
// FormInputFillSuggestions returns a list of form filling suggestions
// for inputs returning the specified recommended values.
func FormInputFillSuggestions(inputs []FormInput) mapsutil.OrderedMap[string, string] {
data := mapsutil.NewOrderedMap[string, string]()
// Fill checkboxes and radioboxes first or default values first
for i, input := range inputs {
switch input.Type {
case "radio":
// Use a single radio name per value
if !data.Has(input.Name) {
data.Set(input.Name, input.Value)
}
case "checkbox":
data.Set(input.Name, input.Value)
default:
// If there is a value, use it for the input. Else
// infer the values based on input types.
if input.Value != "" {
data.Set(input.Name, input.Value)
} else if value, ok := input.Attributes.Get("placeholder"); ok {
inputs[i].Value = value
data.Set(input.Name, value)
}
}
}
// getIntWithdefault returns the integer value of the key or default value
getIntWithdefault := func(input *FormInput, key string, defaultValue int) int {
if value, ok := input.Attributes.Get(key); ok {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}
}
return defaultValue
}
// Fill rest of the inputs based on their types or name and ids
for _, input := range inputs {
if input.Value != "" {
continue
}
switch input.Type {
case "email":
data.Set(input.Name, FormData.Email)
case "color":
data.Set(input.Name, FormData.Color)
case "number", "range":
min := getIntWithdefault(&input, "min", 1)
max := getIntWithdefault(&input, "max", 10)
step := getIntWithdefault(&input, "step", 1)
val := min + step
if val > max {
val = max - step
}
data.Set(input.Name, strconv.Itoa(val))
case "password":
data.Set(input.Name, FormData.Password)
case "tel":
data.Set(input.Name, FormData.Password)
default:
data.Set(input.Name, FormData.Placeholder)
}
}
return data
}
// FormSelectFill fills a map with selected values from a slice of FormSelect structs.
// It iterates over each FormSelect struct in the inputs slice and checks for a selected option.
// If a selected option is found, it adds the corresponding value to the map using the input's name as the key.
// If no option is selected, it selects the first option and adds its value to the map.
// The function returns the filled map.
func FormSelectFill(inputs []FormSelect) mapsutil.OrderedMap[string, string] {
data := mapsutil.NewOrderedMap[string, string]()
for _, input := range inputs {
for _, option := range input.SelectOptions {
if option.Selected != "" {
data.Set(input.Name, option.Value)
break
}
}
// If no option is selected, select the first one
if !data.Has(input.Name) && len(input.SelectOptions) > 0 {
data.Set(input.Name, input.SelectOptions[0].Value)
}
}
return data
}
// FormTextAreaFill fills the form text areas with placeholder values.
// It takes a slice of FormTextArea structs as input and returns an OrderedMap
// containing the form field names as keys and the placeholder values as values.
func FormTextAreaFill(inputs []FormTextArea) mapsutil.OrderedMap[string, string] {
data := mapsutil.NewOrderedMap[string, string]()
for _, input := range inputs {
data.Set(input.Name, FormData.Placeholder)
}
return data
}
// FormFillSuggestions takes a slice of form fields and returns an ordered map
// containing suggestions for filling those form fields. The function iterates
// over each form field and based on its type, calls the corresponding fill
// function to generate suggestions. The suggestions are then merged into a
// single ordered map and returned.
//
// Parameters:
// - formFields: A slice of form fields.
//
// Returns:
// An ordered map containing suggestions for filling the form fields.
func FormFillSuggestions(formFields []interface{}) mapsutil.OrderedMap[string, string] {
merged := mapsutil.NewOrderedMap[string, string]()
for _, item := range formFields {
switch v := item.(type) {
case FormInput:
dataMapInputs := FormInputFillSuggestions([]FormInput{v})
MergeDataMaps(&merged, dataMapInputs)
case FormSelect:
dataMapSelects := FormSelectFill([]FormSelect{v})
MergeDataMaps(&merged, dataMapSelects)
case FormTextArea:
dataMapTextArea := FormTextAreaFill([]FormTextArea{v})
MergeDataMaps(&merged, dataMapTextArea)
}
}
return merged
}
// ConvertGoquerySelectionToFormInput converts goquery selection to form input
func ConvertGoquerySelectionToFormInput(item *goquery.Selection) FormInput {
attrs := item.Nodes[0].Attr
input := FormInput{Attributes: mapsutil.NewOrderedMap[string, string]()}
for _, attribute := range attrs {
switch attribute.Key {
case "name":
input.Name = attribute.Val
case "value":
input.Value = attribute.Val
case "type":
input.Type = attribute.Val
default:
input.Attributes.Set(attribute.Key, attribute.Val)
}
}
return input
}
// ConvertGoquerySelectionToSelectOption converts a goquery.Selection object to a SelectOption object.
// It extracts the attributes from the goquery.Selection object and populates a SelectOption object with the extracted values.
func ConvertGoquerySelectionToSelectOption(item *goquery.Selection) SelectOption {
attrs := item.Nodes[0].Attr
input := SelectOption{Attributes: mapsutil.NewOrderedMap[string, string]()}
for _, attribute := range attrs {
switch attribute.Key {
case "value":
input.Value = attribute.Val
case "selected":
input.Selected = attribute.Key
default:
input.Attributes.Set(attribute.Key, attribute.Val)
}
}
return input
}
// ConvertGoquerySelectionToFormSelect converts a goquery.Selection object to a FormSelect object.
// It extracts the attributes and form options from the goquery.Selection and populates them in the FormSelect object.
// The converted FormSelect object is then returned.
func ConvertGoquerySelectionToFormSelect(item *goquery.Selection) FormSelect {
attrs := item.Nodes[0].Attr
input := FormSelect{Attributes: mapsutil.NewOrderedMap[string, string]()}
for _, attribute := range attrs {
switch attribute.Key {
case "name":
input.Name = attribute.Val
default:
input.Attributes.Set(attribute.Key, attribute.Val)
}
}
input.SelectOptions = []SelectOption{}
item.Find("option").Each(func(_ int, option *goquery.Selection) {
input.SelectOptions = append(input.SelectOptions, ConvertGoquerySelectionToSelectOption(option))
})
return input
}
// ConvertGoquerySelectionToFormTextArea converts a goquery.Selection object to a FormTextArea struct.
// It extracts the attributes from the first node of the selection and populates a FormTextArea object with the extracted data.
// The "name" attribute is assigned to the Name field of the FormTextArea, while other attributes are added to the Attributes map.
func ConvertGoquerySelectionToFormTextArea(item *goquery.Selection) FormTextArea {
attrs := item.Nodes[0].Attr
input := FormTextArea{Attributes: mapsutil.NewOrderedMap[string, string]()}
for _, attribute := range attrs {
switch attribute.Key {
case "name":
input.Name = attribute.Val
default:
input.Attributes.Set(attribute.Key, attribute.Val)
}
}
return input
}
// ConvertGoquerySelectionToFormField converts a goquery.Selection object to a form field.
// It checks the type of the selection and calls the appropriate conversion function.
// If the selection is an input, it calls ConvertGoquerySelectionToFormInput.
// If the selection is a select, it calls ConvertGoquerySelectionToFormSelect.
// If the selection is a textarea, it calls ConvertGoquerySelectionToFormTextArea.
// If the selection is of any other type, it returns nil.
func ConvertGoquerySelectionToFormField(item *goquery.Selection) interface{} {
if item.Is("input") {
return ConvertGoquerySelectionToFormInput(item)
}
if item.Is("select") {
return ConvertGoquerySelectionToFormSelect(item)
}
if item.Is("textarea") {
return ConvertGoquerySelectionToFormTextArea(item)
}
return nil
}
================================================
FILE: pkg/utils/formfill_test.go
================================================
package utils
import (
"net/url"
"strings"
"testing"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/require"
)
var htmlFormInputExample = `
HTML Form Test
User id:
Password:
Kindly Select your favorite color
Red
blue
green
Kindly Select your favourite sports
Cricket
Tennis
Football
Upclick
Start date:
Enter your age:
Enter your Telephone Number(in format of xxx-xxx-xxxx):
Kindly Select your favourite food
Pizza
Burger
Pasta
Kindly Select your favourite country
India
USA
UK
Canada
Write some words about yourself:
Write something here
`
func TestFormInputFillSuggestions(t *testing.T) {
document, err := goquery.NewDocumentFromReader(strings.NewReader(htmlFormInputExample))
require.NoError(t, err, "could not read document")
document.Find("form[action]").Each(func(i int, item *goquery.Selection) {
queryValuesWriter := make(url.Values)
formFields := []interface{}{}
item.Find("input, textarea, select").Each(func(index int, item *goquery.Selection) {
if len(item.Nodes) == 0 {
return
}
formFields = append(formFields, ConvertGoquerySelectionToFormField(item))
})
dataMap := FormFillSuggestions(formFields)
dataMap.Iterate(func(key, value string) bool {
if key == "" || value == "" {
return true
}
queryValuesWriter.Set(key, value)
return true
})
value := queryValuesWriter.Encode()
require.Equal(t, "Startdate=katana&color=green&country=india&firstname=katana&food=pasta&message=katana&num=51&password=katana&sport1=cricket&sport2=tennis&sport3=football&telephone=katanaP%40assw0rd1&upclick=%23a52a2a", value, "could not get correct encoded form")
})
}
================================================
FILE: pkg/utils/jsluice.go
================================================
//go:build !(386 || windows)
package utils
import (
"regexp"
"github.com/BishopFox/jsluice"
)
var (
// CommonJSLibraryFileRegex is a regex to match common js library files.
CommonJSLibraryFileRegex = `(?i)(?:amplify|quantserve|slideshow|jquery|modernizr|polyfill|vendor|modules|gtm|underscore?|tween|retina|selectivizr|cufon|angular|swf|sha1|freestyle|bootstrap|d3|backbone|videojs|google[-_]analytics|material|redux|knockout|datepicker|datetimepicker|ember|react|ng|fusion|analytics|libs?|vendors?|node[-_]modules|lodash|moment|chart|highcharts|raphael|prototype|mootools|dojo|ext|yui|web[-_]?components|polymer|vue|svelte|next|nuxt|gatsby|express|koa|hapi|socket[-_.]?io|axios|superagent|request|bluebird|rxjs|ramda|immutable|flux|redux[-_]saga|mobx|relay|apollo|graphql|three|phaser|pixi|babylon|cannon|hammer|howler|gsap|velocity|mo[-_.]?js|popper|shepherd|prism|highlight|markdown[-_]?it|codemirror|ace[-_]?editor|tinymce|ckeditor|quill|simplemde|monaco[-_]?editor|pdf[-_.]?js|jspdf|fabric|paper|konva|p5|processing|matter[-_.]?js|box2d|planck|chart[-_.]?js|plotly|echarts|d3[-_.]?force|sigma|c3|nvd3|amcharts|vis[-_.]?js|dagre[-_.]?d3|cytoscape|leaflet|openlayers|ol3|mapbox|cesium|turf|moment[-_.]?timezone|luxon|dayjs|date[-_.]?fns|date[-_.]?io|flatpickr|pikaday|fullcalendar|draggable|interact|sortable|dragula|dropzone|filepond|uppy|fine[-_.]?uploader|plyr|mediaelement|flowplayer|jwplayer|video[-_.]?js|mediaelement[-_.]?js|dash[-_.]?js|hls[-_.]?js|videojs|wavesurfer|soundmanager|amplitude|pizzicato|tone|adroll|doubleclick|facebook-pixel|ga-audiences|googlesyndication|adsbygoogle|gpt|amazon-adsystem|criteo|taboola|outbrain|bidswitch|bidswitch.net|spotxchange|yahoo|media.net|contextweb|openx|pubmatic|rubiconproject|indexexchange|appnexus|liveintent|triplelift|verizonmedia|synacor|sonobi|yieldmo|gumgum|smartadserver|mopub|pubnative|inmobi|chartboost|tapjoy|admob|unityads|vungle|flurry|matomy|altitude|dataxu|thetradedesk|exponential|zypmedia|quantcast|mediamath|bidswitch|mgid|revcontent|powerlinks|rhythmone|airpush|smaato|adcolony|mopub|leadbolt|mobfox|nativo|revjet|smartyads|avocarrot|epom|imobile|supersonicads|loopme|applovin|pandora|mytarget|bidvertiser|chitika|popads|propellerads|buysellads|adhit|hilltopads|plugrush|popcash|popunder|revenuehits|trafficjunky|trafficfactory|zero-|smartoasis)(?:[-._][\w\d]*)*\.js$`
commonJSLibraryFileRegexCompiled = regexp.MustCompile(CommonJSLibraryFileRegex)
)
// IsPathCommonJSLibraryFile checks if a given path is a common js library file.
func IsPathCommonJSLibraryFile(path string) bool {
return commonJSLibraryFileRegexCompiled.MatchString(path)
}
type JSLuiceEndpoint struct {
Endpoint string
Type string
}
// ExtractJsluiceEndpoints extracts jsluice endpoints from a given string.
//
// We use tomnomnom and bishopfox's jsluice to extract endpoints from javascript
// files.
//
// We apply several optimizations before running jsluice:
// - We skip common js library files.
// - We skip lines that are too long and contain a lot of characters.
func ExtractJsluiceEndpoints(data string) []JSLuiceEndpoint {
analyzer := jsluice.NewAnalyzer([]byte(data))
// TODO: add new user url matchers
// analyzer.AddURLMatcher(matcher)
var endpoints []JSLuiceEndpoint
foundURLs := analyzer.GetURLs()
for _, url := range foundURLs {
url := url
endpoints = append(endpoints, JSLuiceEndpoint{
Endpoint: url.URL,
Type: url.Type,
})
}
return endpoints
}
================================================
FILE: pkg/utils/jsluice_test.go
================================================
//go:build !(386 || windows)
package utils
import "testing"
func TestIsPathCommonJSLibraryFile(t *testing.T) {
type args struct {
path string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "jquery.js",
args: args{
path: "jquery.js",
},
want: true,
},
{
name: "app.js",
args: args{
path: "app.js",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsPathCommonJSLibraryFile(tt.args.path); got != tt.want {
t.Errorf("IsPathCommonJSLibraryFile() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: pkg/utils/maps.go
================================================
package utils
import mapsutil "github.com/projectdiscovery/utils/maps"
func MergeDataMaps(dataMap1 *mapsutil.OrderedMap[string, string], dataMap2 mapsutil.OrderedMap[string, string]) {
dataMap2.Iterate(func(key, value string) bool {
dataMap1.Set(key, value)
return true
})
}
================================================
FILE: pkg/utils/maps_test.go
================================================
package utils
import (
"testing"
mapsutil "github.com/projectdiscovery/utils/maps"
"github.com/stretchr/testify/require"
)
func TestMergeDataMap(t *testing.T) {
// Create two data maps
dataMap1 := mapsutil.NewOrderedMap[string, string]()
dataMap1.Set("key1", "value1")
dataMap1.Set("key2", "value2")
dataMap2 := mapsutil.NewOrderedMap[string, string]()
dataMap2.Set("key3", "value3")
dataMap2.Set("key4", "value4")
// Merge the data maps
MergeDataMaps(&dataMap1, dataMap2)
// Verify the merged map contains all the keys and values
value1, _ := dataMap1.Get("key1")
value2, _ := dataMap1.Get("key2")
value3, _ := dataMap1.Get("key3")
value4, _ := dataMap1.Get("key4")
require.Equal(t, "value1", value1)
require.Equal(t, "value2", value2)
require.Equal(t, "value3", value3)
require.Equal(t, "value4", value4)
}
================================================
FILE: pkg/utils/pathtrie.go
================================================
package utils
import (
"sync"
lru "github.com/hashicorp/golang-lru/v2"
)
// DefaultPromotionThreshold is the number of distinct children a trie node
// must accumulate before being promoted to a parameter node.
const DefaultPromotionThreshold = 10
// DefaultMaxHosts is the maximum number of hosts tracked by the LRU cache.
// When exceeded, the least recently used host's trie is evicted.
const DefaultMaxHosts = 10000
// PathTrie is a per-host adaptive trie that tracks unique path segments
// at each position. When a node accumulates more distinct children than
// the promotion threshold, it is promoted to a parameter node and all
// future values at that position are collapsed.
//
// Host tracking is bounded by an LRU cache to prevent unbounded memory
// growth during large crawls.
type PathTrie struct {
mu sync.Mutex
roots *lru.Cache[string, *trieNode]
threshold int
}
type trieNode struct {
children map[string]*trieNode
paramChild *trieNode
promoted bool
}
// NewPathTrie creates a new PathTrie with the given promotion threshold.
// If threshold is <= 0, DefaultPromotionThreshold is used.
func NewPathTrie(threshold int) *PathTrie {
if threshold <= 0 {
threshold = DefaultPromotionThreshold
}
cache, _ := lru.New[string, *trieNode](DefaultMaxHosts)
return &PathTrie{
roots: cache,
threshold: threshold,
}
}
// Fingerprint walks the trie for the given host and segments, returning
// a new slice where promoted positions are replaced with "{param}".
// Non-promoted segments are registered in the trie for future cardinality tracking.
func (t *PathTrie) Fingerprint(host string, segments []string) []string {
t.mu.Lock()
defer t.mu.Unlock()
root, ok := t.roots.Get(host)
if !ok {
root = &trieNode{children: make(map[string]*trieNode)}
t.roots.Add(host, root)
}
result := make([]string, len(segments))
current := root
for i, seg := range segments {
if current.promoted {
result[i] = "{param}"
if current.paramChild == nil {
current.paramChild = &trieNode{children: make(map[string]*trieNode)}
}
current = current.paramChild
continue
}
child, exists := current.children[seg]
if !exists {
child = &trieNode{children: make(map[string]*trieNode)}
current.children[seg] = child
if len(current.children) > t.threshold {
current.promoted = true
current.paramChild = &trieNode{children: make(map[string]*trieNode)}
current.children = nil
result[i] = "{param}"
current = current.paramChild
continue
}
}
result[i] = seg
current = child
}
return result
}
================================================
FILE: pkg/utils/pathtrie_test.go
================================================
package utils
import (
"fmt"
"testing"
)
func TestPathTrie_BasicInsertion(t *testing.T) {
trie := NewPathTrie(0)
segments := []string{"api", "v1", "users"}
result := trie.Fingerprint("example.com", segments)
for i, seg := range result {
if seg != segments[i] {
t.Errorf("segment %d = %q, want %q", i, seg, segments[i])
}
}
}
func TestPathTrie_RepeatedInsertionNoop(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
segments := []string{"api", "v1", "users"}
// Inserting the same path many times should not trigger promotion
for range 100 {
result := trie.Fingerprint(host, segments)
for i, seg := range result {
if seg != segments[i] {
t.Fatalf("repeated insertion changed segment %d: got %q, want %q", i, seg, segments[i])
}
}
}
}
func TestPathTrie_PromotionExactThreshold(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
// Insert exactly threshold distinct children — should NOT promote
for i := range DefaultPromotionThreshold {
trie.Fingerprint(host, []string{"items", fmt.Sprintf("item-%d", i)})
}
result := trie.Fingerprint(host, []string{"items", "item-0"})
if result[1] == "{param}" {
t.Error("promoted at exactly threshold, should only promote when exceeding")
}
// One more distinct child triggers promotion
result = trie.Fingerprint(host, []string{"items", "item-trigger"})
if result[1] != "{param}" {
t.Errorf("expected promotion after exceeding threshold, got %q", result[1])
}
}
func TestPathTrie_Promotion(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
for i := range DefaultPromotionThreshold + 1 {
trie.Fingerprint(host, []string{"blog", fmt.Sprintf("slug-%d", i)})
}
result := trie.Fingerprint(host, []string{"blog", "brand-new-slug"})
if result[0] != "blog" {
t.Errorf("static segment = %q, want %q", result[0], "blog")
}
if result[1] != "{param}" {
t.Errorf("promoted segment = %q, want %q", result[1], "{param}")
}
}
func TestPathTrie_PromotionDoesNotAffectOtherHosts(t *testing.T) {
trie := NewPathTrie(0)
for i := range DefaultPromotionThreshold + 1 {
trie.Fingerprint("a.com", []string{"users", fmt.Sprintf("user-%d", i)})
}
result := trie.Fingerprint("b.com", []string{"users", "alice"})
if result[1] != "alice" {
t.Errorf("host isolation failed: got %q, want %q", result[1], "alice")
}
}
func TestPathTrie_DeepPath(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
for i := range DefaultPromotionThreshold + 1 {
trie.Fingerprint(host, []string{"api", "users", fmt.Sprintf("user-%d", i), "posts"})
}
result := trie.Fingerprint(host, []string{"api", "users", "new-user", "posts"})
expected := []string{"api", "users", "{param}", "posts"}
for i, seg := range result {
if seg != expected[i] {
t.Errorf("segment %d = %q, want %q", i, seg, expected[i])
}
}
}
func TestPathTrie_EmptySegments(t *testing.T) {
trie := NewPathTrie(0)
result := trie.Fingerprint("example.com", []string{})
if len(result) != 0 {
t.Errorf("expected empty result, got %v", result)
}
}
func TestPathTrie_SingleSegment(t *testing.T) {
trie := NewPathTrie(0)
for i := range DefaultPromotionThreshold + 1 {
trie.Fingerprint("example.com", []string{fmt.Sprintf("page-%d", i)})
}
result := trie.Fingerprint("example.com", []string{"page-new"})
if result[0] != "{param}" {
t.Errorf("got %q, want {param}", result[0])
}
}
func TestPathTrie_PromotedNodeChildrenNil(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
for i := range DefaultPromotionThreshold + 1 {
trie.Fingerprint(host, []string{fmt.Sprintf("item-%d", i)})
}
root, ok := trie.roots.Get(host)
if !ok {
t.Fatal("expected host root to exist")
}
if root.children != nil {
t.Error("expected children to be nil after promotion")
}
if !root.promoted {
t.Error("expected node to be promoted")
}
}
func TestPathTrie_PreviousValueCollapseAfterPromotion(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
// Insert values before promotion
trie.Fingerprint(host, []string{"blog", "first-post"})
trie.Fingerprint(host, []string{"blog", "second-post"})
// Trigger promotion
for i := 2; i <= DefaultPromotionThreshold; i++ {
trie.Fingerprint(host, []string{"blog", fmt.Sprintf("post-%d", i)})
}
// Previously seen "first-post" should now collapse
result := trie.Fingerprint(host, []string{"blog", "first-post"})
if result[1] != "{param}" {
t.Errorf("previously seen value after promotion: got %q, want {param}", result[1])
}
}
func TestPathTrie_SegmentsAfterPromotedNodeTrackedIndependently(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
// Promote the username segment: /users/{param}/...
for i := range DefaultPromotionThreshold + 1 {
trie.Fingerprint(host, []string{"users", fmt.Sprintf("user-%d", i), "profile"})
}
// After username is promoted, segments AFTER it should still track independently
// Feed "profile" and "settings" — two distinct values, should not promote
trie.Fingerprint(host, []string{"users", "someone", "settings"})
result := trie.Fingerprint(host, []string{"users", "anyone", "settings"})
if result[1] != "{param}" {
t.Errorf("promoted segment: got %q, want {param}", result[1])
}
// "settings" should still be literal since paramChild only has 2 children
if result[2] == "{param}" {
t.Errorf("segment after promoted should not be promoted yet: got %q", result[2])
}
}
func TestPathTrie_MultipleBranchesIndependent(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
// Promote /api/users/* but not /api/posts/*
for i := range DefaultPromotionThreshold + 1 {
trie.Fingerprint(host, []string{"api", "users", fmt.Sprintf("user-%d", i)})
}
// Only add a few posts
trie.Fingerprint(host, []string{"api", "posts", "first"})
trie.Fingerprint(host, []string{"api", "posts", "second"})
// users should be promoted
result := trie.Fingerprint(host, []string{"api", "users", "new"})
if result[2] != "{param}" {
t.Errorf("users branch should be promoted: got %q", result[2])
}
// posts should NOT be promoted
result = trie.Fingerprint(host, []string{"api", "posts", "third"})
if result[2] == "{param}" {
t.Errorf("posts branch should not be promoted: got %q", result[2])
}
}
func TestPathTrie_MultiplePromotionsAtDifferentDepths(t *testing.T) {
trie := NewPathTrie(0)
host := "example.com"
// Promote at depth 0 (root children)
for i := range DefaultPromotionThreshold + 1 {
trie.Fingerprint(host, []string{fmt.Sprintf("section-%d", i), "page"})
}
result := trie.Fingerprint(host, []string{"section-new", "page"})
if result[0] != "{param}" {
t.Errorf("depth 0 promotion: got %q, want {param}", result[0])
}
// Depth 1 should still track normally through paramChild
// "page" is the only value at depth 1 through paramChild, so not promoted
if result[1] == "{param}" {
t.Errorf("depth 1 should not be promoted yet: got %q", result[1])
}
}
func TestPathTrie_NewHostLazyInit(t *testing.T) {
trie := NewPathTrie(0)
// Accessing new host should work without explicit init
result := trie.Fingerprint("new-host.com", []string{"foo", "bar"})
if result[0] != "foo" || result[1] != "bar" {
t.Errorf("lazy init failed: got %v", result)
}
// Verify the host was created in the LRU cache
if !trie.roots.Contains("new-host.com") {
t.Error("host root not created after first access")
}
}
func TestPathTrie_CustomThreshold(t *testing.T) {
trie := NewPathTrie(3)
host := "example.com"
// With threshold=3, promotion happens after 4 distinct children
trie.Fingerprint(host, []string{"items", "a"})
trie.Fingerprint(host, []string{"items", "b"})
trie.Fingerprint(host, []string{"items", "c"})
// 3 children, not promoted yet
result := trie.Fingerprint(host, []string{"items", "a"})
if result[1] == "{param}" {
t.Error("should not promote at threshold")
}
// 4th distinct child triggers promotion
result = trie.Fingerprint(host, []string{"items", "d"})
if result[1] != "{param}" {
t.Errorf("expected promotion with threshold=3, got %q", result[1])
}
}
func TestPathTrie_LRUEviction(t *testing.T) {
// Create a trie with a small LRU for testing eviction behavior
trie := NewPathTrie(0)
// Add many hosts to verify the trie doesn't panic
for i := range 100 {
host := fmt.Sprintf("host-%d.com", i)
trie.Fingerprint(host, []string{"path", "segment"})
}
// All recently accessed hosts should still work
result := trie.Fingerprint("host-99.com", []string{"path", "segment"})
if result[0] != "path" || result[1] != "segment" {
t.Errorf("LRU host access failed: got %v", result)
}
}
================================================
FILE: pkg/utils/queue/priority_queue.go
================================================
package queue
import (
"container/heap"
)
type priorityQueue struct {
itemHeap *itemHeap
}
func newPriorityQueue() *priorityQueue {
return &priorityQueue{itemHeap: &itemHeap{}}
}
// Len returns the number of elements in the queue.
func (p *priorityQueue) Len() int {
return p.itemHeap.Len()
}
func (p *priorityQueue) Push(v interface{}, priority int) {
newItem := &item{
value: v,
priority: priority,
}
heap.Push(p.itemHeap, newItem)
}
func (p *priorityQueue) Pop() interface{} {
if len(*p.itemHeap) == 0 {
return nil
}
item := heap.Pop(p.itemHeap).(*item)
return item.value
}
type itemHeap []*item
type item struct {
value interface{}
priority int
index int
}
func (ih *itemHeap) Len() int {
return len(*ih)
}
func (ih *itemHeap) Less(i, j int) bool {
// Less since we are executing lower prioritize values first and
// higher priority values in the end.
return (*ih)[i].priority < (*ih)[j].priority
}
func (ih *itemHeap) Swap(i, j int) {
(*ih)[i], (*ih)[j] = (*ih)[j], (*ih)[i]
(*ih)[i].index = i
(*ih)[j].index = j
}
func (ih *itemHeap) Push(x interface{}) {
it := x.(*item)
it.index = len(*ih)
*ih = append(*ih, it)
}
func (ih *itemHeap) Pop() interface{} {
old := *ih
item := old[len(old)-1]
*ih = old[0 : len(old)-1]
return item
}
================================================
FILE: pkg/utils/queue/priority_queue_test.go
================================================
package queue
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestPriorityQueue(t *testing.T) {
queue := newPriorityQueue()
queue.Push("lower", 3)
queue.Push("higher", 4)
queue.Push("lowest", 1)
require.Equal(t, "lowest", queue.Pop(), "could not pop lowest priority first")
require.Equal(t, "lower", queue.Pop(), "could not pop lower priority first")
require.Equal(t, "higher", queue.Pop(), "could not pop higher priority first")
}
================================================
FILE: pkg/utils/queue/queue.go
================================================
package queue
import (
"errors"
"sync"
"time"
)
// Queue is a queue that implements bucket based depth-first
// or breadth-first queue.
//
// The breadth-first queues allow defining scores on whose
// basis the bucket is distributed. Lower scores are picked up first, and
// higher scores which have a greater chance of being just random
// noise are picked up later in depth first.
//
// Depth-first queue uses a simple stack for LIFO operations and distributes
// items as they come in.
type Queue struct {
sync.Mutex
Timeout time.Duration
Strategy Strategy
stack *stack
priorityQueue *priorityQueue
}
// New creates a new queue from the type specified.
func New(strategyName string, timeout int) (*Queue, error) {
strategy, ok := strategiesMap[strategyName]
if !ok {
return nil, errors.New("unsupported strategy")
}
queue := &Queue{
Strategy: strategy,
Timeout: time.Duration(timeout) * time.Second,
stack: newStack(),
priorityQueue: newPriorityQueue(),
}
return queue, nil
}
// Len returns the number of items in queue.
func (q *Queue) Len() int {
q.Lock()
defer q.Unlock()
switch q.Strategy {
case BreadthFirst:
return q.priorityQueue.Len()
case DepthFirst:
return q.stack.Len()
}
return 0
}
// Push pushes an element with an optional priority into the queue.
func (q *Queue) Push(x interface{}, priority int) {
q.Lock()
defer q.Unlock()
switch q.Strategy {
case BreadthFirst:
q.priorityQueue.Push(x, priority)
case DepthFirst:
q.stack.Push(x)
}
}
// Pop pops an element from the queue. Result can be nil if no more
// elements are present in the queue.
func (q *Queue) Pop() chan interface{} {
items := make(chan interface{})
go func() {
start := time.Now()
for {
var item interface{}
q.Lock()
switch q.Strategy {
case BreadthFirst:
item = q.priorityQueue.Pop()
case DepthFirst:
item = q.stack.Pop()
}
q.Unlock()
if item == nil {
if !start.Add(q.Timeout).Before(time.Now()) {
time.Sleep(1 * time.Second)
continue
}
close(items)
return
} else {
items <- item
start = time.Now()
}
}
}()
return items
}
================================================
FILE: pkg/utils/queue/stack.go
================================================
package queue
import (
"container/list"
)
// Taken from https://stackoverflow.com/a/64641330/9546749
type stack struct {
ll *list.List
}
func newStack() *stack {
return &stack{ll: list.New()}
}
func (s *stack) Push(x interface{}) {
s.ll.PushBack(x)
}
func (s *stack) Len() int {
return s.ll.Len()
}
func (s *stack) Pop() interface{} {
if s.ll.Len() == 0 {
return nil
}
tail := s.ll.Back()
val := tail.Value
s.ll.Remove(tail)
return val
}
================================================
FILE: pkg/utils/queue/stack_test.go
================================================
package queue
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestStack(t *testing.T) {
queue := newStack()
queue.Push("lower")
queue.Push("higher")
queue.Push("lowest")
require.Equal(t, "lowest", queue.Pop(), "could not pop correct value")
require.Equal(t, "higher", queue.Pop(), "could not pop correct value")
require.Equal(t, "lower", queue.Pop(), "could not pop correct value")
}
================================================
FILE: pkg/utils/queue/strategy.go
================================================
package queue
// Strategy of the queue
type Strategy int
// strategies of queues available for selection.
const (
BreadthFirst Strategy = iota
DepthFirst
)
func (s Strategy) String() string {
for k, v := range strategiesMap {
if v == s {
return k
}
}
return ""
}
var strategiesMap = map[string]Strategy{
"breadth-first": BreadthFirst,
"depth-first": DepthFirst,
}
================================================
FILE: pkg/utils/regex.go
================================================
package utils
import (
"regexp"
)
var (
BodyA0 = `(?:`
BodyB0 = `(`
BodyC0 = `(?:[\.]{1,2}/[A-Za-z0-9\-_/\\?&@\.?=%]+)`
BodyC1 = `|(https?://[A-Za-z0-9_\-\.]+([\.]{0,2})?\/[A-Za-z0-9\-_/\\?&@\.?=%]+)`
BodyC2 = `|(/[A-Za-z0-9\-_/\\?&@\.%]+\.(aspx?|action|cfm|cgi|do|pl|css|x?html?|js(p|on)?|pdf|php5?|py|rss))`
BodyC3 = `|([A-Za-z0-9\-_?&@\.%]+/[A-Za-z0-9/\\\-_?&@\.%]+\.(aspx?|action|cfm|cgi|do|pl|css|x?html?|js(p|on)?|pdf|php5?|py|rss))`
BodyB1 = `)`
BodyA1 = `)`
// pageBodyRegex extracts endpoints from page body
pageBodyRegex = regexp.MustCompile(BodyA0 + BodyB0 + BodyC0 + BodyC1 + BodyC2 + BodyC3 + BodyB1 + BodyA1)
JsA0 = `(?:"|'|\s)`
JsB0 = `(`
JsC0 = `((https?://[A-Za-z0-9_\-.]+(?:\:\d{1,5})?)+([\.]{1,2})?/[A-Za-z0-9/\-_\\.%]+(?:[\?|#][^"']+)?)`
JsC1 = `|((\.{1,2}/)?[a-zA-Z0-9\-_/\\%]+\.(aspx?|js(?:on|p)?|html|php5?|action|do)(?:[\?|#][^"']+)?)`
JsC2 = `|((\.{0,2}/)[a-zA-Z0-9\-_/\\%]+(?:/|\\)[a-zA-Z0-9\-_]{3,}(?:[\?|#][^"']+)?)`
JsC3 = `|((\.{0,2})[a-zA-Z0-9\-_/\\%]{3,}/)`
JsB1 = `)`
JsA1 = `(?:"|'|\s)`
// relativeEndpointsRegex is the regex to find endpoints in js files.
relativeEndpointsRegex = regexp.MustCompile(JsA0 + JsB0 + JsC0 + JsC1 + JsC2 + JsC3 + JsB1 + JsA1)
)
// ExtractBodyEndpoints extracts body endpoints from a data item
func ExtractBodyEndpoints(data string) []string {
matches := []string{}
unique := make(map[string]struct{})
relativeMatches := pageBodyRegex.FindAllStringSubmatch(data, -1)
for _, match := range relativeMatches {
if len(match) < 2 {
continue
}
if _, ok := unique[match[1]]; ok {
continue
}
unique[match[1]] = struct{}{}
matches = append(matches, match[1])
}
return matches
}
// ExtractRelativeEndpoints extracts relative endpoints from a data item
func ExtractRelativeEndpoints(data string) []string {
matches := []string{}
unique := make(map[string]struct{})
relativeMatches := relativeEndpointsRegex.FindAllStringSubmatch(data, -1)
for _, match := range relativeMatches {
if len(match) < 2 {
continue
}
if _, ok := unique[match[1]]; ok {
continue
}
unique[match[1]] = struct{}{}
matches = append(matches, match[1])
}
return matches
}
================================================
FILE: pkg/utils/regex_test.go
================================================
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPageBodyRegex(t *testing.T) {
testCases := []struct {
name string
input string
expected []string
}{
{
name: "Mix of patterns",
input: `Some text link1 and also http://a.com/b.html and https://c.com/d.aspx?p=1 finally /abs/path.js and rel/path/script.py end`,
expected: []string{
"./rel/file.txt", // BodyC0
"../rel2/file.php", // BodyC0
"http://a.com/b.html", // BodyC1
"https://c.com/d.aspx?p=1", // BodyC1
"/abs/path.js", // BodyC2
"rel/path/script.py", // BodyC3
},
},
{
name: "No matches",
input: "Just some plain text without any URLs or paths.",
expected: []string{},
},
{
name: "Only BodyC0",
input: `"./path1" '../path2'`,
expected: []string{"./path1", "../path2"},
},
{
name: "Only BodyC1",
input: `http://example.com/page1 https://secure.com/page2`,
expected: []string{"http://example.com/page1", "https://secure.com/page2"},
},
{
name: "Only BodyC2",
input: `"/path/to/file.css" '/another/script.js'`,
expected: []string{"/path/to/file.css", "/another/script.js"},
},
{
name: "Only BodyC3",
input: `"relative/path/file.php" 'another/relative/page.html'`,
expected: []string{"relative/path/file.php", "another/relative/page.html"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
matches := pageBodyRegex.FindAllStringSubmatch(tc.input, -1)
actual := []string{}
for _, match := range matches {
if len(match) > 1 {
actual = append(actual, match[1]) // Extract the first capture group
}
}
assert.ElementsMatch(t, tc.expected, actual, "Input: %s", tc.input)
})
}
}
func TestRelativeEndpointsRegex(t *testing.T) {
testCases := []struct {
name string
input string
expected []string
}{
{
name: "Mix of patterns in JS-like context",
input: `var u1 = "https://d.com/e.php?q=1"; let u2 = './f/g.js'; const u3 = '../h/i.html'; func('/j/k/lll'); load('m/nnn/'); action("o/p.action");`,
expected: []string{
"https://d.com/e.php?q=1", // JsC0
"./f/g.js", // JsC1
"../h/i.html", // JsC1
"/j/k/lll", // JsC2
"m/nnn/", // JsC3
"o/p.action", // JsC1
},
},
{
name: "No matches",
input: "var x = 1; let y = 'hello'; const z = true;",
expected: []string{},
},
{
name: "Only JsC0",
input: `"https://example.com/api/v1?key=123" 'http://localhost:8080/test#section'`,
expected: []string{"https://example.com/api/v1?key=123", "http://localhost:8080/test#section"},
},
{
name: "Only JsC1",
input: `"./script.js" 'page.php?id=5'`,
expected: []string{"./script.js", "page.php?id=5"},
},
{
name: "Only JsC2",
input: `"/api/v2/users" '/data/items/fetch'`,
expected: []string{"/api/v2/users", "/data/items/fetch"},
},
{
name: "Only JsC3",
input: `"./images/" '../assets/' "static/"`,
expected: []string{"./images/", "../assets/", "static/"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
matches := relativeEndpointsRegex.FindAllStringSubmatch(tc.input, -1)
actual := []string{}
for _, match := range matches {
if len(match) > 1 {
actual = append(actual, match[1]) // Extract the first capture group
}
}
assert.ElementsMatch(t, tc.expected, actual, "Input: %s", tc.input)
})
}
}
================================================
FILE: pkg/utils/scope/scope.go
================================================
package scope
import (
"fmt"
"net"
"net/url"
"regexp"
"strings"
"golang.org/x/net/publicsuffix"
)
// Manager manages scope for crawling process
type Manager struct {
inScope []*regexp.Regexp
outOfScope []*regexp.Regexp
noScope bool
fieldScope dnsScopeField
fieldScopePattern *regexp.Regexp
}
type dnsScopeField int
const (
dnDnsScopeField dnsScopeField = iota + 1
rdnDnsScopeField
fqdnDNSScopeField
customDNSScopeField
)
var stringToDNSScopeField = map[string]dnsScopeField{
"dn": dnDnsScopeField,
"rdn": rdnDnsScopeField,
"fqdn": fqdnDNSScopeField,
}
// NewManager returns a new scope manager for crawling
func NewManager(inScope, outOfScope []string, fieldScope string, noScope bool) (*Manager, error) {
manager := &Manager{
noScope: noScope,
}
if scopeValue, ok := stringToDNSScopeField[fieldScope]; !ok {
manager.fieldScope = customDNSScopeField
if compiled, err := regexp.Compile(fieldScope); err != nil {
return nil, fmt.Errorf("could not compile regex %s: %s", fieldScope, err)
} else {
manager.fieldScopePattern = compiled
}
} else {
manager.fieldScope = scopeValue
}
for _, regex := range inScope {
if compiled, err := regexp.Compile(regex); err != nil {
return nil, fmt.Errorf("could not compile regex %s: %s", regex, err)
} else {
manager.inScope = append(manager.inScope, compiled)
}
}
for _, regex := range outOfScope {
if compiled, err := regexp.Compile(regex); err != nil {
return nil, fmt.Errorf("could not compile regex %s: %s", regex, err)
} else {
manager.outOfScope = append(manager.outOfScope, compiled)
}
}
return manager, nil
}
// Validate returns true if the URL matches scope rules.
// When noScope is true, DNS validation is skipped but URL-based scope rules still apply.
func (m *Manager) Validate(URL *url.URL, rootHostname string) (bool, error) {
if !m.noScope {
// Only validate DNS if scope is enabled
hostname := URL.Hostname()
dnsValidated, err := m.validateDNS(hostname, rootHostname)
if err != nil || !dnsValidated {
return false, err
}
}
if len(m.inScope) > 0 || len(m.outOfScope) > 0 {
urlValidated, err := m.validateURL(URL.String())
if err != nil || !urlValidated {
return false, err
}
}
return true, nil
}
// validateURL checks whether the given URL matches the configured inScope and outOfScope patterns.
// It returns true if the URL is allowed (matches inScope and doesn't match outOfScope),
// false if rejected, and an error if pattern matching fails.
// When both inScope and outOfScope are empty, it returns true with no error.
func (m *Manager) validateURL(URL string) (bool, error) {
for _, item := range m.outOfScope {
if item.MatchString(URL) {
return false, nil
}
}
if len(m.inScope) == 0 {
return true, nil
}
var inScopeMatched bool
for _, item := range m.inScope {
if item.MatchString(URL) {
inScopeMatched = true
break
}
}
return inScopeMatched, nil
}
// validateDNS performs DNS-based scope validation by checking if the URL's hostname
// matches the configured host-based scope rules. It returns true if the hostname
// is within scope, false if out of scope, and an error if DNS resolution or
// validation fails.
func (m *Manager) validateDNS(hostname, rootHostname string) (bool, error) {
parsed := net.ParseIP(hostname)
if m.fieldScope == customDNSScopeField {
// If we have a custom regex, we need to match it against the full hostname
if m.fieldScopePattern.MatchString(hostname) {
return true, nil
}
}
if m.fieldScope == fqdnDNSScopeField || parsed != nil {
matched := strings.EqualFold(hostname, rootHostname)
return matched, nil
}
rdn, dn, err := getDomainRDNandRDN(rootHostname)
if err != nil {
return false, err
}
switch m.fieldScope {
case dnDnsScopeField:
return strings.Contains(hostname, dn), nil
case rdnDnsScopeField:
return strings.HasSuffix(hostname, rdn), nil
}
return false, nil
}
// getDomainRDNandRDN extracts and returns the root domain name (RDN) and the
// effective top-level domain plus one label (eTLD+1) from the given hostname.
// It returns empty strings and an error if the hostname cannot be parsed.
func getDomainRDNandRDN(domain string) (string, string, error) {
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") || strings.Contains(domain, "..") {
return "", "", fmt.Errorf("publicsuffix: empty label in domain %q", domain)
}
suffix, _ := publicsuffix.PublicSuffix(domain)
if len(domain) <= len(suffix) {
return domain, "", nil
}
i := len(domain) - len(suffix) - 1
if domain[i] != '.' {
return domain, "", nil
}
return domain[1+strings.LastIndex(domain[:i], "."):], domain[1+strings.LastIndex(domain[:i], ".") : len(domain)-len(suffix)-1], nil
}
================================================
FILE: pkg/utils/scope/scope_test.go
================================================
package scope
import (
"testing"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/stretchr/testify/require"
)
// TestManagerValidate verifies the Manager's Validate method with various scope configurations,
// including URL pattern matching and host-based DNS validation for different scope types (dn, rdn, fqdn).
func TestManagerValidate(t *testing.T) {
t.Run("url", func(t *testing.T) {
manager, err := NewManager([]string{`example`}, []string{`logout\.php`}, "dn", false)
require.NoError(t, err, "could not create scope manager")
parsed, _ := urlutil.Parse("https://test.com/index.php/example")
validated, err := manager.Validate(parsed.URL, "test.com")
require.NoError(t, err, "could not validate url")
require.True(t, validated, "could not get correct in-scope validation")
parsed, _ = urlutil.Parse("https://test.com/logout.php")
validated, err = manager.Validate(parsed.URL, "another.com")
require.NoError(t, err, "could not validate url")
require.False(t, validated, "could not get correct out-scope validation")
})
t.Run("host", func(t *testing.T) {
t.Run("dn", func(t *testing.T) {
manager, err := NewManager(nil, nil, "dn", false)
require.NoError(t, err, "could not create scope manager")
parsed, _ := urlutil.Parse("https://testanother.com/index.php")
validated, err := manager.Validate(parsed.URL, "test.com")
require.NoError(t, err, "could not validate host")
require.True(t, validated, "could not get correct in-scope validation")
})
t.Run("rdn", func(t *testing.T) {
manager, err := NewManager(nil, nil, "rdn", false)
require.NoError(t, err, "could not create scope manager")
parsed, _ := urlutil.Parse("https://subdomain.example.com/logout.php")
validated, err := manager.Validate(parsed.URL, "example.com")
require.NoError(t, err, "could not validate host")
require.True(t, validated, "could not get correct in-scope validation")
})
t.Run("localhost", func(t *testing.T) {
manager, err := NewManager(nil, nil, "rdn", false)
require.NoError(t, err, "could not create scope manager")
parsed, _ := urlutil.Parse("http://localhost:8082/logout.php")
validated, err := manager.Validate(parsed.URL, "localhost")
require.NoError(t, err, "could not validate host")
require.True(t, validated, "could not get correct in-scope validation")
})
t.Run("fqdn", func(t *testing.T) {
manager, err := NewManager(nil, nil, "fqdn", false)
require.NoError(t, err, "could not create scope manager")
parsed, _ := urlutil.Parse("https://test.com/index.php")
validated, err := manager.Validate(parsed.URL, "test.com")
require.NoError(t, err, "could not validate host")
require.True(t, validated, "could not get correct in-scope validation")
parsed, _ = urlutil.Parse("https://subdomain.example.com/logout.php")
validated, err = manager.Validate(parsed.URL, "example.com")
require.NoError(t, err, "could not validate host")
require.False(t, validated, "could not get correct out-scope validation")
parsed, _ = urlutil.Parse("https://example.com/logout.php")
validated, err = manager.Validate(parsed.URL, "another.com")
require.NoError(t, err, "could not validate host")
require.False(t, validated, "could not get correct out-scope validation")
})
})
}
// TestGetDomainRDNandDN verifies the extraction of root domain name (RDN) and
// effective top-level domain plus one label (eTLD+1) from a hostname.
func TestGetDomainRDNandDN(t *testing.T) {
rdn, dn, err := getDomainRDNandRDN("test.projectdiscovery.io")
require.Nil(t, err, "could not get domain rdn and dn")
require.Equal(t, "projectdiscovery.io", rdn, "could not get correct rdn")
require.Equal(t, "projectdiscovery", dn, "could not get correct dn")
}
// TestNoScopeWithOutOfScope verifies that when noScope is enabled, host-based
// DNS validation is bypassed while URL pattern matching (inScope/outOfScope rules)
// continues to function correctly. It tests scenarios with only outOfScope patterns
// and with both inScope and outOfScope patterns to ensure proper filtering behavior.
func TestNoScopeWithOutOfScope(t *testing.T) {
t.Run("noScope with outOfScope rules", func(t *testing.T) {
// Create manager with noScope=true and outOfScope patterns
outOfScopePatterns := []string{
`logout\.php`,
`/admin/`,
`\.js$`,
`^https?://[^/]+/\?lang=[a-z]{2}`,
}
manager, err := NewManager(nil, outOfScopePatterns, "rdn", true)
require.NoError(t, err, "could not create scope manager with noScope and outOfScope")
// Test 1: URL from different domain should be allowed (noScope ignores DNS)
parsed, _ := urlutil.Parse("https://completely-different.com/index.php")
validated, err := manager.Validate(parsed.URL, "original.com")
require.NoError(t, err, "could not validate cross-domain URL with noScope")
require.True(t, validated, "cross-domain URL should be allowed with noScope")
// Test 2: URL matching outOfScope pattern should be rejected
parsed, _ = urlutil.Parse("https://completely-different.com/logout.php")
validated, err = manager.Validate(parsed.URL, "original.com")
require.NoError(t, err, "could not validate outOfScope URL")
require.False(t, validated, "outOfScope pattern should still be applied with noScope")
// Test 3: Normal URLs should be allowed
parsed, _ = urlutil.Parse("https://any-site.com/products/item123")
validated, err = manager.Validate(parsed.URL, "original.com")
require.NoError(t, err, "could not validate normal URL")
require.True(t, validated, "normal URLs should be allowed")
})
t.Run("noScope with both inScope and outOfScope", func(t *testing.T) {
// Test combining noScope with both inScope and outOfScope
inScopePatterns := []string{`/api/`, `/products/`}
outOfScopePatterns := []string{`/api/internal/`, `\.css$`}
manager, err := NewManager(inScopePatterns, outOfScopePatterns, "fqdn", true)
require.NoError(t, err, "could not create manager with both scope types")
// Should be allowed: matches inScope, doesn't match outOfScope
parsed, _ := urlutil.Parse("https://external.com/api/users")
validated, err := manager.Validate(parsed.URL, "original.com")
require.NoError(t, err, "could not validate API endpoint")
require.True(t, validated, "API endpoint should be allowed")
// Should be rejected: matches both inScope and outOfScope (outOfScope wins)
parsed, _ = urlutil.Parse("https://external.com/api/internal/secrets")
validated, err = manager.Validate(parsed.URL, "original.com")
require.NoError(t, err, "could not validate internal API")
require.False(t, validated, "internal API should be excluded by outOfScope")
// Should be rejected: doesn't match inScope
parsed, _ = urlutil.Parse("https://external.com/about/company")
validated, err = manager.Validate(parsed.URL, "original.com")
require.NoError(t, err, "could not validate about page")
require.False(t, validated, "about page should be rejected (not in inScope)")
// Should be rejected: matches outOfScope
parsed, _ = urlutil.Parse("https://external.com/styles/main.css")
validated, err = manager.Validate(parsed.URL, "original.com")
require.NoError(t, err, "could not validate CSS file")
require.False(t, validated, "CSS files should be excluded")
})
}
================================================
FILE: pkg/utils/urlfingerprint.go
================================================
package utils
import (
"net/url"
"regexp"
"sort"
"strings"
)
// segmentPattern defines a regex pattern and its replacement placeholder
// for identifying variable path segments.
type segmentPattern struct {
regex *regexp.Regexp
placeholder string
// validate provides an additional check beyond the regex match.
// When nil, the regex match alone is sufficient.
validate func(string) bool
}
// containsHexLetter returns true if the string has at least one a-f/A-F character.
// Pure-numeric strings that happen to be ≥8 digits should fall through to
// timestamp/numeric patterns instead of matching as hex.
func containsHexLetter(s string) bool {
for _, c := range s {
if (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') {
return true
}
}
return false
}
// segmentPatterns are tested in order from most specific to most general.
// Each pattern is anchored (^...$) to match complete path segments only.
var segmentPatterns = []segmentPattern{
{regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`), "{uuid}", nil},
{regexp.MustCompile(`^[0-9a-fA-F]{64}$`), "{sha256}", containsHexLetter},
{regexp.MustCompile(`^[0-9a-fA-F]{40}$`), "{sha1}", containsHexLetter},
{regexp.MustCompile(`^[0-9a-fA-F]{32}$`), "{md5}", containsHexLetter},
{regexp.MustCompile(`^[0-9a-fA-F]{24}$`), "{oid}", containsHexLetter},
{regexp.MustCompile(`^[0-9a-fA-F]{8,}$`), "{hex}", containsHexLetter},
{regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`), "{date}", nil},
{regexp.MustCompile(`^\d{10}(\d{3})?$`), "{ts}", nil},
{regexp.MustCompile(`^\d+$`), "{num}", nil},
}
// normalizeSegment checks a single path segment against heuristic patterns.
// Returns the placeholder if matched, or the original segment if no pattern matches.
func normalizeSegment(segment string) (string, bool) {
for _, p := range segmentPatterns {
if p.regex.MatchString(segment) {
if p.validate != nil && !p.validate(segment) {
continue
}
return p.placeholder, true
}
}
return segment, false
}
// FingerprintURL produces a structural fingerprint of the given URL by:
// 1. Replacing variable path segments (IDs, UUIDs, hashes, dates) with placeholders
// 2. Using the adaptive trie (if provided) to detect learned parameter positions
// 3. Dropping query parameter values, keeping only sorted keys
//
// When trie is nil, only Layer 1 regex-based normalization is applied.
func FingerprintURL(rawURL string, trie *PathTrie) string {
u, err := url.Parse(rawURL)
if err != nil {
return rawURL
}
path := u.Path
if path == "" || path == "/" {
return buildFingerprint(u, path)
}
// Split path into segments, preserving leading slash
trimmed := strings.Trim(path, "/")
if trimmed == "" {
return buildFingerprint(u, "/")
}
segments := strings.Split(trimmed, "/")
// Layer 1: heuristic regex normalization
for i, seg := range segments {
if placeholder, matched := normalizeSegment(seg); matched {
segments[i] = placeholder
}
}
// Layer 2: adaptive trie normalization
if trie != nil {
segments = trie.Fingerprint(u.Hostname(), segments)
}
fingerprintedPath := "/" + strings.Join(segments, "/")
if strings.HasSuffix(path, "/") {
fingerprintedPath += "/"
}
return buildFingerprint(u, fingerprintedPath)
}
// buildFingerprint reconstructs the URL with the fingerprinted path
// and sorted query keys (values dropped).
func buildFingerprint(u *url.URL, path string) string {
var b strings.Builder
if u.Scheme != "" {
b.WriteString(u.Scheme)
b.WriteString("://")
}
b.WriteString(u.Host)
b.WriteString(path)
if u.RawQuery != "" {
keys := sortedQueryKeys(u.Query())
if len(keys) > 0 {
b.WriteByte('?')
b.WriteString(strings.Join(keys, "&"))
}
}
return b.String()
}
// sortedQueryKeys extracts and sorts query parameter keys.
func sortedQueryKeys(params url.Values) []string {
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
================================================
FILE: pkg/utils/urlfingerprint_test.go
================================================
package utils
import (
"fmt"
"strings"
"testing"
)
func TestContainsHexLetter(t *testing.T) {
tests := []struct {
input string
want bool
}{
{"abcdef01", true},
{"ABCDEF01", true},
{"1234abcd", true},
{"12345678", false},
{"00000000", false},
{"0000000a", true},
{"", false},
{"g", false}, // not a hex letter
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
if got := containsHexLetter(tt.input); got != tt.want {
t.Errorf("containsHexLetter(%q) = %v, want %v", tt.input, got, tt.want)
}
})
}
}
func TestNormalizeSegment(t *testing.T) {
tests := []struct {
segment string
want string
matched bool
}{
// UUID
{"550e8400-e29b-41d4-a716-446655440000", "{uuid}", true},
{"550E8400-E29B-41D4-A716-446655440000", "{uuid}", true},
// UUID with all-numeric groups still matches (dashes are the anchor)
{"12345678-1234-1234-1234-123456789012", "{uuid}", true},
// SHA256
{"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "{sha256}", true},
// 64-digit pure numeric should NOT match sha256 — falls to {num}
{"1234567890123456789012345678901234567890123456789012345678901234", "{num}", true},
// SHA1
{"da39a3ee5e6b4b0d3255bfef95601890afd80709", "{sha1}", true},
// 40-digit pure numeric should NOT match sha1 — falls to {ts} or {num}
{"1234567890123456789012345678901234567890", "{num}", true},
// MD5
{"d41d8cd98f00b204e9800998ecf8427e", "{md5}", true},
// 32-digit pure numeric should NOT match md5 — falls to {num}
{"12345678901234567890123456789012", "{num}", true},
// ObjectId (MongoDB 24 hex chars)
{"507f1f77bcf86cd799439011", "{oid}", true},
// 24-digit pure numeric — falls to {num}
{"123456789012345678901234", "{num}", true},
// Long hex (>= 8 chars, requires hex letter)
{"abcdef01", "{hex}", true},
{"1234abcd5678", "{hex}", true},
{"DEADBEEF", "{hex}", true},
// 8-digit pure numeric — NOT hex, falls to {num}
{"12345678", "{num}", true},
// 9-digit pure numeric — NOT hex, falls to {num}
{"123456789", "{num}", true},
// ISO date
{"2024-01-15", "{date}", true},
{"2023-12-31", "{date}", true},
{"1999-01-01", "{date}", true},
// Not a date (wrong format) — pure digits, no hex letters, falls to {num}
{"20240115", "{num}", true},
// Timestamp (10 digits)
{"1704067200", "{ts}", true},
// Timestamp (13 digits)
{"1704067200000", "{ts}", true},
// 11 digits — not a timestamp, falls to {num}
{"17040672001", "{num}", true},
// 14 digits — not a timestamp, falls to {num}
{"17040672000001", "{num}", true},
// Numeric
{"123", "{num}", true},
{"0", "{num}", true},
{"999999", "{num}", true},
{"1", "{num}", true},
// Non-matching
{"users", "users", false},
{"api", "api", false},
{"v1", "v1", false},
{"v2", "v2", false},
{"my-awesome-post", "my-awesome-post", false},
{"image123.jpg", "image123.jpg", false},
{"style.css", "style.css", false},
{"index.html", "index.html", false},
{"abcdef", "abcdef", false}, // 6-char hex, below 8-char threshold
{"feedback", "feedback", false}, // valid hex chars but also contains non-hex
{"deadbeef-cafe", "deadbeef-cafe", false}, // dash in wrong position, not UUID format
{"", "", false},
}
for _, tt := range tests {
t.Run(tt.segment, func(t *testing.T) {
got, matched := normalizeSegment(tt.segment)
if got != tt.want || matched != tt.matched {
t.Errorf("normalizeSegment(%q) = (%q, %v), want (%q, %v)",
tt.segment, got, matched, tt.want, tt.matched)
}
})
}
}
func TestFingerprintURL_RegexOnly(t *testing.T) {
tests := []struct {
name string
url string
want string
}{
{
name: "numeric path segments",
url: "https://example.com/api/v1/users/123/posts/456",
want: "https://example.com/api/v1/users/{num}/posts/{num}",
},
{
name: "uuid in path",
url: "https://example.com/product/550e8400-e29b-41d4-a716-446655440000",
want: "https://example.com/product/{uuid}",
},
{
name: "date in path",
url: "https://example.com/archive/2024-01-15/article",
want: "https://example.com/archive/{date}/article",
},
{
name: "query params sorted and values dropped",
url: "https://example.com/search?z=1&a=2&m=3",
want: "https://example.com/search?a&m&z",
},
{
name: "no variable segments unchanged",
url: "https://example.com/about/team",
want: "https://example.com/about/team",
},
{
name: "root path",
url: "https://example.com/",
want: "https://example.com/",
},
{
name: "empty path",
url: "https://example.com",
want: "https://example.com",
},
{
name: "mixed pattern types in one path",
url: "https://example.com/users/42/posts/da39a3ee5e6b4b0d3255bfef95601890afd80709",
want: "https://example.com/users/{num}/posts/{sha1}",
},
{
name: "trailing slash preserved",
url: "https://example.com/api/v1/users/123/",
want: "https://example.com/api/v1/users/{num}/",
},
{
name: "timestamp in path",
url: "https://example.com/events/1704067200",
want: "https://example.com/events/{ts}",
},
// Scheme and port variations
{
name: "http scheme",
url: "http://example.com/items/99",
want: "http://example.com/items/{num}",
},
{
name: "url with port",
url: "https://example.com:8443/api/users/42",
want: "https://example.com:8443/api/users/{num}",
},
// Fragment should be stripped by url.Parse (not included in fingerprint)
{
name: "fragment stripped",
url: "https://example.com/page/123#section",
want: "https://example.com/page/{num}",
},
// Combined path normalization + query normalization
{
name: "variable path with query params",
url: "https://example.com/users/42/posts?sort=date&page=1",
want: "https://example.com/users/{num}/posts?page&sort",
},
// Multiple different pattern types
{
name: "uuid then numeric then date",
url: "https://example.com/obj/550e8400-e29b-41d4-a716-446655440000/rev/5/date/2024-01-15",
want: "https://example.com/obj/{uuid}/rev/{num}/date/{date}",
},
{
name: "md5 hash in path",
url: "https://cdn.example.com/assets/d41d8cd98f00b204e9800998ecf8427e/image.png",
want: "https://cdn.example.com/assets/{md5}/image.png",
},
{
name: "sha256 hash in path",
url: "https://example.com/blobs/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
want: "https://example.com/blobs/{sha256}",
},
{
name: "mongodb objectid in path",
url: "https://example.com/docs/507f1f77bcf86cd799439011",
want: "https://example.com/docs/{oid}",
},
{
name: "long hex token in path",
url: "https://example.com/verify/abcdef0123456789",
want: "https://example.com/verify/{hex}",
},
{
name: "13-digit timestamp",
url: "https://example.com/snapshot/1704067200000",
want: "https://example.com/snapshot/{ts}",
},
// Edge cases
{
name: "single segment numeric",
url: "https://example.com/42",
want: "https://example.com/{num}",
},
{
name: "query with no path segments",
url: "https://example.com/?q=test",
want: "https://example.com/?q",
},
{
name: "single query param",
url: "https://example.com/search?q=hello",
want: "https://example.com/search?q",
},
{
name: "file extension not affected",
url: "https://example.com/assets/image123.jpg",
want: "https://example.com/assets/image123.jpg",
},
{
name: "deeply nested numeric ids",
url: "https://example.com/a/1/b/2/c/3/d/4",
want: "https://example.com/a/{num}/b/{num}/c/{num}/d/{num}",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FingerprintURL(tt.url, nil)
if got != tt.want {
t.Errorf("FingerprintURL(%q, nil)\n got = %q\n want = %q", tt.url, got, tt.want)
}
})
}
}
func TestFingerprintURL_Idempotency(t *testing.T) {
urls := []string{
"https://example.com/users/123/posts/456",
"https://example.com/product/550e8400-e29b-41d4-a716-446655440000",
"https://example.com/search?z=1&a=2",
"https://example.com/about/team",
}
for _, rawURL := range urls {
first := FingerprintURL(rawURL, nil)
second := FingerprintURL(first, nil)
if first != second {
t.Errorf("not idempotent for %q:\n first = %q\n second = %q", rawURL, first, second)
}
}
}
func TestFingerprintURL_SimilarURLsCollapse(t *testing.T) {
// The core use case: structurally identical URLs should produce the same fingerprint
groups := [][]string{
{
"https://example.com/users/1/profile",
"https://example.com/users/2/profile",
"https://example.com/users/999/profile",
},
{
"https://example.com/product/550e8400-e29b-41d4-a716-446655440000",
"https://example.com/product/6ba7b810-9dad-11d1-80b4-00c04fd430c8",
},
{
"https://example.com/search?q=foo&page=1",
"https://example.com/search?page=5&q=bar",
},
}
for _, group := range groups {
fingerprints := make(map[string]bool)
for _, u := range group {
fp := FingerprintURL(u, nil)
fingerprints[fp] = true
}
if len(fingerprints) != 1 {
t.Errorf("URLs in group did not collapse to single fingerprint: %v → %v", group, fingerprints)
}
}
}
func TestFingerprintURL_DifferentURLsStayDistinct(t *testing.T) {
// Structurally different URLs should NOT collapse
pairs := [][2]string{
{
"https://example.com/users/123/profile",
"https://example.com/users/123/settings",
},
{
"https://example.com/api/v1/users",
"https://example.com/api/v2/users",
},
{
"https://example.com/search?q=test",
"https://example.com/search?q=test&page=1",
},
}
for _, pair := range pairs {
fp1 := FingerprintURL(pair[0], nil)
fp2 := FingerprintURL(pair[1], nil)
if fp1 == fp2 {
t.Errorf("structurally different URLs collapsed:\n %q → %q\n %q → %q", pair[0], fp1, pair[1], fp2)
}
}
}
func TestFingerprintURL_WithTrie_PromotionLifecycle(t *testing.T) {
trie := NewPathTrie(0)
host := "https://example.com"
// Before promotion: each slug is kept as-is
for i := range DefaultPromotionThreshold {
fp := FingerprintURL(fmt.Sprintf("%s/blog/post-%d", host, i), trie)
expected := fmt.Sprintf("%s/blog/post-%d", host, i)
if fp != expected {
t.Fatalf("before promotion: got %q, want %q", fp, expected)
}
}
// Trigger promotion with one more distinct slug
FingerprintURL(host+"/blog/the-trigger", trie)
// After promotion: all new slugs should collapse
got := FingerprintURL(host+"/blog/never-seen-before", trie)
want := host + "/blog/{param}"
if got != want {
t.Errorf("after promotion: got %q, want %q", got, want)
}
// Previously seen slugs also collapse after promotion
got = FingerprintURL(host+"/blog/post-0", trie)
if got != want {
t.Errorf("previously seen slug after promotion: got %q, want %q", got, want)
}
}
func TestFingerprintURL_WithTrie_RegexSegmentsDontInflateTrie(t *testing.T) {
trie := NewPathTrie(0)
// Feed many numeric IDs — regex catches them before trie sees them
for i := range 100 {
FingerprintURL(fmt.Sprintf("https://example.com/items/%d/details", i), trie)
}
// A non-numeric slug should NOT be promoted since all prior values
// were normalized to "{num}" by regex before reaching the trie
result := FingerprintURL("https://example.com/items/brand-new-slug/details", trie)
if strings.Contains(result, "{param}") {
t.Errorf("regex-detected segments leaked to trie: got %q", result)
}
}
func TestFingerprintURL_WithTrie_MultipleHosts(t *testing.T) {
trie := NewPathTrie(0)
// Promote /users/* on host A
for i := range DefaultPromotionThreshold + 1 {
FingerprintURL(fmt.Sprintf("https://a.com/users/user-%d", i), trie)
}
// Host B should be unaffected
got := FingerprintURL("https://b.com/users/alice", trie)
want := "https://b.com/users/alice"
if got != want {
t.Errorf("host isolation: got %q, want %q", got, want)
}
// Host A should still collapse
got = FingerprintURL("https://a.com/users/new-user", trie)
want = "https://a.com/users/{param}"
if got != want {
t.Errorf("host A after promotion: got %q, want %q", got, want)
}
}
func TestFingerprintURL_WithTrie_DeepPromotion(t *testing.T) {
trie := NewPathTrie(0)
// Promote only the username segment, not others
for i := range DefaultPromotionThreshold + 1 {
FingerprintURL(fmt.Sprintf("https://example.com/api/users/user-%d/posts", i), trie)
}
got := FingerprintURL("https://example.com/api/users/new-user/posts", trie)
want := "https://example.com/api/users/{param}/posts"
if got != want {
t.Errorf("deep promotion: got %q, want %q", got, want)
}
// The "api" and "posts" segments should NOT be promoted
got = FingerprintURL("https://example.com/api/users/another/settings", trie)
if !strings.HasPrefix(got, "https://example.com/api/users/{param}/") {
t.Errorf("static segments should stay: got %q", got)
}
}
func TestFingerprintURL_WithTrie_CombinedRegexAndTrie(t *testing.T) {
trie := NewPathTrie(0)
// Promote blog slugs
for i := range DefaultPromotionThreshold + 1 {
FingerprintURL(fmt.Sprintf("https://example.com/blog/slug-%d", i), trie)
}
// URL with both regex-detectable (numeric) and trie-promoted (slug) segments
got := FingerprintURL("https://example.com/blog/my-article", trie)
want := "https://example.com/blog/{param}"
if got != want {
t.Errorf("trie promotion: got %q, want %q", got, want)
}
// Numeric IDs should still use regex placeholder, not trie
got = FingerprintURL("https://example.com/items/42", trie)
want = "https://example.com/items/{num}"
if got != want {
t.Errorf("regex on separate path: got %q, want %q", got, want)
}
}
func TestFingerprintURL_InvalidURL(t *testing.T) {
// Malformed URL should return the original string
inputs := []string{
"://invalid",
"",
}
for _, raw := range inputs {
got := FingerprintURL(raw, nil)
if raw == "" {
// url.Parse("") succeeds with empty result
continue
}
if got != raw {
t.Errorf("FingerprintURL(%q) = %q, want original returned", raw, got)
}
}
}
func TestFingerprintURL_NilTrieSameAsRegexOnly(t *testing.T) {
urls := []string{
"https://example.com/users/123",
"https://example.com/about",
"https://example.com/search?q=test&page=1",
}
for _, u := range urls {
withNil := FingerprintURL(u, nil)
withEmpty := FingerprintURL(u, NewPathTrie(0))
if withNil != withEmpty {
t.Errorf("nil trie vs empty trie differ for %q:\n nil = %q\n empty = %q", u, withNil, withEmpty)
}
}
}
================================================
FILE: pkg/utils/utils.go
================================================
package utils
import (
"fmt"
"strings"
"github.com/lukasbob/srcset"
"github.com/projectdiscovery/gologger"
urlutil "github.com/projectdiscovery/utils/url"
)
// IsURL returns true if a provided string is URL
func IsURL(url string) bool {
if value, err := urlutil.Parse(url); err == nil {
return value.Hostname() != ""
} else {
gologger.Debug().Msgf("IsURL: failed to parse url %v got %v", url, err)
}
return false
}
// ParseSRCSetTag parses srcset tag returning found URLs
func ParseSRCSetTag(value string) []string {
set := srcset.Parse(value)
values := make([]string, 0, len(set))
for _, item := range set {
values = append(values, item.URL)
}
return values
}
// ParseLinkTag parses link tag values returning found urls
//
// Inspired from: https://github.com/tomnomnom/linkheader
func ParseLinkTag(value string) []string {
urls := make([]string, 0)
for _, chunk := range strings.Split(value, ",") {
for _, piece := range strings.Split(chunk, ";") {
piece = strings.Trim(piece, " ")
if piece == "" {
continue
}
if piece[0] == '<' && piece[len(piece)-1] == '>' {
urls = append(urls, strings.Trim(piece, "<>"))
continue
}
}
}
return urls
}
// ParseRefreshTag parses refresh tag values returning found urls
func ParseRefreshTag(value string) string {
chunks := strings.Split(value, "url=")
if len(chunks) < 2 {
return ""
}
chunk := chunks[1]
chunk = strings.TrimSuffix(chunk, ";")
if chunk == "" {
return ""
}
return chunk
}
// WebUserAgent returns the chrome-web user agent
func WebUserAgent() string {
return "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
}
func FlattenHeaders(headers map[string][]string) map[string]string {
h := make(map[string]string)
for k, v := range headers {
h[k] = strings.Join(v, ";")
}
return h
}
// ReplaceAllQueryParam replaces all the query param with the given value
func ReplaceAllQueryParam(reqUrl, val string) string {
u, err := urlutil.Parse(reqUrl)
if err != nil {
return reqUrl
}
params := u.Query()
params.Iterate(func(key string, value []string) bool {
params.Set(key, "")
return true
})
u.RawQuery = params.Encode()
return u.String()
}
// ExtractParentPaths returns all path directories for a given URL
func ExtractParentPaths(rawurl string) []string {
u, err := urlutil.Parse(rawurl)
if err != nil {
return nil
}
path := u.Path
if path == "" || path == "/" {
return nil
}
path = strings.Trim(path, "/")
parts := strings.Split(path, "/")
if len(parts) == 0 {
return nil
}
urls := []string{}
for i := len(parts) - 1; i > 0; i-- {
if parentPath := strings.Join(parts[:i], "/"); parentPath != "" {
urls = append(urls, fmt.Sprintf("%s://%s/%s", u.Scheme, u.Host, parentPath))
}
}
return urls
}
================================================
FILE: pkg/utils/utils_test.go
================================================
package utils
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseLinkTag(t *testing.T) {
header := "; rel=\"next\"," +
"; rel=\"last\""
values := ParseLinkTag(header)
require.ElementsMatch(t, []string{"https://api.github.com/user/58276/repos?page=2", "https://api.github.com/user/58276/repos?page=10"}, values, "could not parse correct links")
}
func TestParseRefreshTag(t *testing.T) {
header := "999; url=/test/headers/refresh.found"
values := ParseRefreshTag(header)
require.Equal(t, "/test/headers/refresh.found", values, "could not parse correct links")
}
func TestExtractParentPaths(t *testing.T) {
urls := ExtractParentPaths("https://example.com/test/path/to/file.html")
require.ElementsMatch(t, []string{"https://example.com/test/path/to", "https://example.com/test/path", "https://example.com/test"}, urls, "could not extract correct parent paths")
}