A small, read-only AppSec scanner for finding accidentally exposed credentials in Confluence pages, page history, comments, and text attachments.
Status: functional MVP, validated against Confluence Data Center 7.13.7 and suitable for controlled scans. Review Known limitations before using it against a production Confluence instance.
The scanner reports metadata and stable fingerprints. By default it omits the matched secret and surrounding source text from text, JSON, and SARIF output. Plaintext matches can be requested explicitly for local review; baseline and diagnostic log output always remain secret-safe.
- paginated discovery of spaces and current pages through Confluence REST API v1;
- explicit space inclusion/exclusion and conservative defaults for personal and archived spaces;
- optional historical page-version scanning;
- optional scanning of current footer, inline, and resolved comments;
- optional scanning of supported text attachments with a hard size limit;
- Bearer token and HTTP Basic authentication;
- custom CA bundles without disabling TLS verification;
- versioned YAML configuration with strict validation and CLI overrides;
- read-only preflight checks for authentication, REST API shape, and requested content capabilities;
- 27 built-in rules covering common tokens, keys, connection strings, private keys, and labelled credentials;
- rule-specific context, entropy thresholds, allowlists, and stopwords;
- deduplication of the same finding across page versions;
- retry/backoff for rate limits and transient server failures;
- interactive progress on stderr without corrupting JSON or SARIF output;
- rotating diagnostic logs with secret-safe HTTP request metadata;
- bounded REST responses, document sizes, regex execution, finding counts, and total runtime for safer operation on large installations;
- text, JSON, and SARIF 2.1.0 reports to stdout or private atomic files;
- explicit opt-in plaintext match output for controlled local review;
- versioned baselines for suppressing reviewed findings;
- CI-friendly exit codes.
- Browser/SSO sessions, OAuth, Kerberos, client certificates, and manually supplied cookies are not supported.
- The client targets the REST API v1 response shape; support outside the versions listed under Compatibility is not yet verified.
- Only Confluence
body.storagepage content is extracted. - Comment history is not scanned; only comments currently returned for each page are inspected.
- Attachments are limited to UTF-8 and BOM-marked UTF-16 text. PDF, Office, archives, images, and other binary formats are skipped.
- Historical scanning performs a request for each candidate version and can be expensive on large spaces.
- Scans are full-scope within the selected spaces; there is no modified-since or CQL-based incremental mode yet.
- Regex and entropy detection can produce false positives and false negatives. This is a secret scanner, not a general-purpose DLP system.
- Findings are reported but not revoked, deleted, or sent to an issue tracker.
- Python 3.9 or newer;
- network access from the scanner to the Confluence REST API;
- either a Bearer token or credentials accepted through HTTP Basic auth;
- read access to every space, page, historical version, comment, or attachment included in the scan.
Use a dedicated least-privilege account where possible.
| Confluence edition | Status |
|---|---|
| Data Center 7.13.7 | Preflight and scanning validated against a real installation. |
| Other Data Center releases | Not yet tested systematically; run --preflight before scanning. |
| Confluence Cloud | REST API v1 is targeted, but end-to-end compatibility has not yet been verified. |
Compatibility depends on the enabled endpoints, authentication provider, and permissions of the configured account. A successful preflight validates a small sample, not every page or optional content type in the installation.
From the repository root:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install .This installs the confluleaks command in the active environment. For editable
development with a current pip version, use python -m pip install -e ..
Running python -m confluleaks directly from the checkout is also supported.
Authentication secrets are read only from environment variables. Bearer is the default and is appropriate for Confluence Data Center personal access tokens:
export CONFLUENCE_URL='https://confluence.example.com'
export CONFLUENCE_TOKEN='<personal-access-token>'
confluleaks --space ENGBasic authentication uses CONFLUENCE_USERNAME as the user ID and
CONFLUENCE_TOKEN as the password or API token. For
Confluence Cloud,
use the account email as the username and an API token as the secret:
export CONFLUENCE_URL='https://example.atlassian.net/wiki'
export CONFLUENCE_AUTH='basic'
export CONFLUENCE_USERNAME='scanner@example.com'
export CONFLUENCE_TOKEN='<api-token-or-password>'
confluleaks --space ENGSSO plugins may define their own token scheme. For example, Kantega SSO Enterprise API tokens are used as the Basic-auth password together with the underlying Confluence username, not as Bearer tokens.
--auth bearer or --auth basic overrides CONFLUENCE_AUTH. Bearer is the
default. Prefer a dedicated least-privilege account and tokens over reusable
account passwords.
HTTPS certificates are verified by default. If Confluence uses an internal certificate authority, provide a readable PEM CA bundle containing the trusted root and any required intermediate certificates:
export CONFLUENCE_CA_BUNDLE='/etc/ssl/certs/company-ca.pem'
confluleaks --preflight --space ENGThe equivalent command-line option is --ca-bundle PATH, and configuration
files accept ca_bundle. Relative configuration paths are resolved from the
configuration file's directory. Confluleaks deliberately has no insecure
-k/--no-verify mode; install the corporate CA into the trust store used by
Python/Requests or provide an explicit bundle.
When the configured URL uses plain HTTP, an interactive run prints a warning
and continues only after an explicit y/yes response. Non-interactive runs
fail closed instead of waiting for input. For deliberately isolated local
testing or automation, --allow-insecure-http bypasses the prompt but still
prints and logs the warning. It does not disable certificate verification for
HTTPS.
Copy the tracked example to create a local configuration:
cp confluleaks.example.yaml confluleaks.yamlconfluleaks.yaml in the current working directory is loaded automatically and
is ignored by Git. To use a different file, pass --config PATH or set
CONFLULEAKS_CONFIG. Pass --no-config to ignore both the environment-selected
file and the local file.
Settings are resolved in this order, from highest to lowest priority:
- command-line options;
CONFLUENCE_URL,CONFLUENCE_AUTH,CONFLUENCE_CA_BUNDLE, andCONFLULEAKS_LOG_FILE;- the selected YAML file;
- built-in defaults.
For repeatable --space and --exclude-space options, the first command-line
occurrence replaces the whole configured list. Boolean settings can be
overridden in either direction, for example --comments or --no-comments.
Relative rules, baseline, ca_bundle, and log_file paths are resolved
from the configuration file's directory rather than the process working
directory.
--preflight, --output, and --write-baseline remain command-line-only
actions.
The schema is flat, uses version: 1, and accepts the option names shown in
confluleaks.example.yaml. Unknown fields, invalid
types, unsupported versions, and unreadable selected files fail closed before
any Confluence request is made.
Tokens, passwords, API tokens, and usernames are deliberately rejected in the
YAML file. Keep authentication material in CONFLUENCE_TOKEN and, for Basic
auth, CONFLUENCE_USERNAME. The example file is safe to commit; a populated
local confluleaks.yaml is not intended to be committed even though it cannot
contain authentication credentials.
Before scanning a real installation, verify the connection and the exact scope you plan to enable:
confluleaks --preflight --space ENGThis verifies the current user through /rest/api/user/current, rejects an
anonymous session, checks the REST API v1 collection shape, verifies direct
access to the requested space, and inspects one current page response. Add
scope flags to probe their endpoints too:
confluleaks \
--preflight \
--space ENG \
--comments \
--attachments \
--history \
--format jsonComment and attachment checks fetch at most one metadata/content record from
their collection endpoints; attachments are not downloaded. The history check
requests at most one previous version of the sample page. If no --space is
provided, the first visible space and one of its pages are used as samples.
PASS means the authenticated account was recognized and the sampled endpoint
returned the expected REST API v1 shape. User identity fields are never written
to preflight output.
WARN means a capability could not be verified because there was no suitable
sample, such as a previous page version; warnings still return exit code 0.
FAIL covers rejected authentication, missing permissions, unavailable
resources, or incompatible response fields and returns exit code 1.
Preflight does not load detection rules or baselines, scan content, download
attachments, apply fail_on, or write baselines. It is a compatibility probe,
not proof that the account can read every page in a large installation. Output
is available as text or json; SARIF is intentionally reserved for actual
findings.
Set the Confluence base URL and token:
export CONFLUENCE_URL='https://confluence.example.com'
export CONFLUENCE_TOKEN='<bearer-token>'For installations under a context path, include it in the URL, for example
https://confluence.example.com/confluence. The configured base must expose
the scanner endpoints below <base>/rest/api/....
Start with a small, current-page-only scan:
confluleaks --space ENG --max-pages 100If --max-pages prevents the scanner from processing another discovered page,
the result is marked as incomplete and the process exits with code 1. This is
useful for evaluation, but an incomplete result cannot be written as a
baseline.
After reviewing the initial behavior, expand the scope deliberately:
confluleaks \
--space ENG \
--space OPS \
--comments \
--attachments \
--history-limit 5 \
--request-delay 0.1 \
--format jsonInteractive scans report progress to stderr automatically. The final text, JSON, or SARIF report remains isolated on stdout and can still be redirected to a file. Progress includes space discovery and selection, the current space, periodic page/version/comment/attachment counters, candidate finding counts, elapsed time, and the last space reached before a request failure.
# Force progress when stderr is redirected or the process is not attached to a TTY.
confluleaks --progress --format json > confluleaks-report.json
# Disable progress explicitly, for example in CI.
confluleaks --no-progress --format sarif > confluleaks.sarifFor long broad scans, --continue-on-error preserves a partial report and moves
on to other spaces after recoverable failures. The partial result still exits
with status 1 and must not be treated as a clean scan.
For a persistent execution trace, enable a rotating diagnostic log. debug
records each logical HTTP request and response in addition to scan stages:
confluleaks \
--progress \
--log-file logs/confluleaks.log \
--log-level debug \
--continue-on-error \
--format json \
> confluleaks-report.jsonLog entries contain UTC timestamps and JSON event payloads. Request events include the HTTP method, REST endpoint, safe scope/pagination parameters, timeout, status, response size, elapsed time, and request attempt count. Logs never include the authentication header, token, response body, page title, source content, or matched secret. They do contain internal URLs, space keys, content IDs, local configuration paths, and aggregate finding counts, so treat them as internal security data.
New log files use owner-only permissions where the operating system supports
them. Logs rotate at 10 MiB and retain three backups. CONFLULEAKS_LOG_FILE
and the YAML fields log_file / log_level are also supported; relative YAML
paths are resolved from the configuration file's directory. info records
scan stages, while debug adds request-level detail.
By default the scanner discovers all spaces but scans only current global spaces. Personal and archived spaces are skipped.
# Scan only selected spaces. The option can be repeated.
confluleaks --space ENG --space OPS
# Scan all default spaces except selected ones.
confluleaks --exclude-space PUBLIC
# Expand the default selection.
confluleaks --include-personal-spaces --include-archived-spacesAn explicitly selected --space is scanned even if it is personal or archived.
Explicit keys are resolved through direct space lookups, so a scoped scan does
not enumerate every space in a large installation. The same key cannot be both
included and excluded.
History is opt-in:
# All available previous versions.
confluleaks --history
# At most five previous versions per current page.
confluleaks --history-limit 5Passing --history-limit automatically enables history. Missing historical
versions that return 404 are skipped. Identical findings across versions are
collapsed and retain the list of matching version numbers.
--no-history overrides history settings from confluleaks.yaml, including a
configured history_limit.
Comment scanning is opt-in and covers the current comments returned by the Confluence REST API, including footer, inline, and resolved locations:
confluleaks --commentsComments are fetched page by page. Findings identify the parent page and
comment ID, but do not include the comment body, author, or a surrounding
snippet. The matched value is omitted by default and included only with the
explicitly unsafe --show-secrets option. Comment edit history is not scanned.
Attachment scanning is also opt-in:
# Supported text attachments up to 5 MiB each.
confluleaks --attachments
# Override the per-attachment limit.
confluleaks --attachments --max-attachment-size-mb 1Supported extensions are .cfg, .conf, .csv, .env, .ini, .js,
.json, .log, .md, .properties, .py, .sh, .sql, .ts, .txt,
.xml, .yaml, and .yml. Known text MIME types are also accepted.
An explicit binary MIME type takes precedence over a text-looking filename. Declared and actually downloaded bytes are both checked against the limit. Cross-origin attachment links are rejected before a request is made. Skipping an oversized supported attachment marks the scan as incomplete.
Human-readable output is the default:
confluleaks --format textScanner-native JSON:
confluleaks --format json --output confluence-results.json--output PATH writes text, JSON, SARIF, and preflight reports atomically
instead of sending them to stdout. Missing parent directories are created, and
the resulting file is restricted to the current user (0600 on POSIX
systems). An existing report is replaced only after the new report is complete.
Matched values are omitted by default. For a controlled local review, use the
explicitly unsafe --show-secrets mode:
mkdir -p results
chmod 700 results
umask 077
confluleaks --show-secrets --format json \
--output results/confluence-secrets.jsonEach finding then contains matched_value in JSON, Matched value in text,
or matchedValue in SARIF. The scanner prints a warning to stderr whenever
this mode is active. Plaintext matches are retained in memory only while this
mode is enabled; they are never copied into diagnostic logs or baselines.
Treat the resulting report as credential material: keep it local, restrict its
permissions, do not upload SARIF produced this way, and delete it after the
credentials have been rotated or revoked.
Reported line and column numbers refer to the scanner's extracted plain text, not to line numbers shown by the Confluence editor or storage-format HTML.
SARIF 2.1.0:
confluleaks --format sarif --output confluence-results.sarifSARIF output contains rule descriptors, severity, confidence, page, comment, or attachment locations, and stable partial fingerprints. It validates against the OASIS SARIF 2.1.0 schema. Because findings point to Confluence URLs rather than repository files, repository-centric SARIF consumers may display the result without annotating a source file in the repository.
Use --fail-on to make findings affect CI:
confluleaks --format sarif --fail-on high \
--output confluence-results.sarif| Exit code | Meaning |
|---|---|
0 |
Scan completed and no unsuppressed finding reached --fail-on. |
1 |
Configuration, baseline, or Confluence error; also returned for any incomplete result caused by an operational guardrail or --continue-on-error. |
2 |
At least one unsuppressed finding reached the configured severity threshold. |
Critical findings satisfy every threshold. Severity order is low, medium,
high, critical.
A baseline suppresses reviewed findings so CI can focus on newly introduced ones.
Create it only after reviewing a complete scan of the intended scope:
confluleaks \
--space ENG \
--comments \
--attachments \
--write-baseline confluence-baseline.jsonUse the same scope on subsequent runs:
confluleaks \
--space ENG \
--comments \
--attachments \
--baseline confluence-baseline.json \
--format sarif \
--fail-on high > confluence-results.sarifBaseline entries contain an opaque finding ID, rule ID, page ID, and optional comment or attachment ID. They do not contain matched values, source snippets, titles, or raw secret fingerprints.
Identity is stable across page renames, new page versions, and line movement.
The same match on another page or attachment is treated as new. Baselines are
written atomically with owner-only permissions (0600). Writing is refused if
the result contains errors or was made incomplete by any operational
guardrail.
Keep the selected scope consistent. A successful but intentionally narrow scan is complete for that scope and can therefore replace a broader baseline if the same output path is used.
The default rules live in
scanner/default_rules.yaml. Supply a different
file with --rules:
confluleaks --rules ./organization-rules.yamlMinimal rule:
rules:
- id: internal-service-token
name: Internal service token
severity: high
confidence: 0.9
regex: '\b(?P<secret>svc_[A-Za-z0-9]{32})\b'Supported fields:
| Field | Required | Meaning |
|---|---|---|
id |
yes | Stable unique rule identifier. |
severity |
yes | low, medium, high, or critical. |
regex |
yes | Python regular expression. |
name |
no | Display name; defaults to id. |
confidence |
no | Number from 0 to 1; default 0.8. |
keywords |
no | Lower-cased context hints. |
require_context |
no | Require at least one keyword near the match. |
context_radius |
no | Characters inspected on each side; default 120. |
min_entropy |
no | Reject values below this Shannon entropy. |
allowlist.regexes |
no | Case-insensitive patterns applied to the matched value. |
allowlist.stopwords |
no | Case-insensitive substrings that suppress a match. |
Rule files are validated strictly: unknown fields, non-finite numbers,
non-boolean require_context values, empty-matching expressions, and non-string
keyword or allowlist entries are rejected before a scan begins.
Prefer a named secret capture group so only the credential value is
fingerprinted. Alternatives may use groups named secret_*. Without either,
the entire regex match is treated as the secret.
Example false-positive controls:
min_entropy: 3.2
keywords:
- api_key
- token
require_context: true
allowlist:
regexes:
- '^(?:x+|0+|\*+|<.+>)$'
stopwords:
- example
- placeholder
- redactedChanging a rule ID changes finding fingerprints and baseline identities. Treat rule IDs as persistent API identifiers once a baseline is in use.
The client retries 429, 500, 502, 503, and 504 responses with
exponential backoff and honors Retry-After. Terminal errors distinguish HTTP
status failures, per-request timeouts, exhausted connection retries, interrupted
response streams, redirect loops, and TLS verification failures. Collection
errors include safe pagination context such as the space key and start offset.
confluleaks \
--retries 5 \
--backoff 1 \
--request-delay 0.2 \
--timeout 30--continue-on-error records recoverable request failures and continues with
other spaces or pages. The report remains partial and the process exits with
code 1.
The scanner applies conservative limits by default so an unexpectedly large page, response, pathological rule, or finding storm cannot consume memory and CPU without a bound.
| Option | Default | Behavior when reached |
|---|---|---|
--max-response-size-mb |
16 MiB | Abort that streamed REST response before retaining more bytes. |
--max-document-size-mb |
5 MiB | Skip that page, version, comment, or decoded attachment. |
--regex-timeout |
0.25 s | Bound each regex operation; on timeout, stop the rule and skip the rest of that document. |
--max-findings-per-document |
1,000 | Retain the first matches and omit the rest from that document. |
--max-findings |
10,000 | Stop the entire scan once additional matches are observed. |
--max-runtime |
3,600 s | Stop between scan units after the soft wall-clock budget expires. |
The attachment download limit is controlled separately by
--max-attachment-size-mb and has the same incomplete-result semantics. The
runtime limit is soft: a single HTTP request can run until --timeout, and a
single regex operation can run until --regex-timeout before the
total-runtime check runs again.
Every triggered guardrail sets truncated: true, records a stable identifier
in truncation_reasons in JSON and SARIF, and returns exit code 1. Text output
prints the reasons as Result incomplete. This prevents a bounded scan from
being mistaken for a clean complete scan or used to write a baseline.
Run confluleaks --help for the authoritative CLI help.
confluleaks [OPTIONS]
--url URL override CONFLUENCE_URL
--auth {bearer,basic} authentication method (default: bearer)
--ca-bundle PATH PEM CA bundle for TLS verification
--allow-insecure-http bypass the HTTP confirmation prompt (unsafe)
--config PATH load this YAML configuration
--no-config ignore environment-selected and local YAML files
--preflight check authentication and REST capabilities
--version print the installed version
--rules PATH load a YAML rules file
--format {text,json,sarif} output format (default: text)
--output PATH atomically write report instead of stdout
--show-secrets / --no-show-secrets
include plaintext matches (unsafe; default: off)
--page-size N pagination size (default: 50; capped at 200)
--timeout SECONDS request timeout (default: 20)
--space KEY include only KEY; repeatable
--exclude-space KEY exclude KEY; repeatable
--include-personal-spaces / --no-include-personal-spaces
control personal-space discovery
--include-archived-spaces / --no-include-archived-spaces
control archived-space discovery
--max-pages N stop after N current pages
--history / --no-history control historical page-version scanning
--history-limit N scan at most N previous versions per page
--comments / --no-comments control current comment scanning
--attachments / --no-attachments
control supported text attachment scanning
--max-attachment-size-mb MB attachment limit (default: 5 MiB)
--max-response-size-mb MB REST response limit (default: 16 MiB)
--max-document-size-mb MB content body limit (default: 5 MiB)
--regex-timeout SECONDS per-operation regex timeout (default: 0.25)
--max-findings N global finding limit (default: 10000)
--max-findings-per-document N
per-document finding limit (default: 1000)
--max-runtime SECONDS soft scan runtime limit (default: 3600)
--continue-on-error / --no-continue-on-error
control partial results after recoverable errors
--retries N transient request retries (default: 3)
--backoff SECONDS retry backoff factor (default: 0.5)
--request-delay SECONDS minimum delay between request starts
--progress / --no-progress control progress on stderr (default: auto)
--log-file PATH rotating diagnostic log file
--log-level LEVEL debug, info, warning, or error (default: info)
--baseline PATH suppress known findings
--write-baseline PATH atomically write a baseline
--fail-on SEVERITY return exit code 2 at this threshold
Confluence REST API
│
▼
ConfluenceClient ── pagination / retry / throttling / response limits
│
├── PreflightChecker ── capability checks ── text / JSON
▼
SecretScanner ──── scope / current pages / history / comments / attachments
│
├── HTML and text extraction
▼
Detector ───────── bounded regex / context / entropy / allowlists
│
▼
deduplicated Findings
│
├── optional baseline suppression
▼
text / JSON / SARIF ── exit threshold
The detector operates on a small document model and does not make Confluence requests. This keeps data access, extraction, detection, and reporting separate enough to refactor independently.
CLI arguments, environment variables, and the optional versioned YAML file are
merged and validated before ConfluenceClient is constructed.
- Authentication secrets are read only from
CONFLUENCE_TOKEN; Basic auth also readsCONFLUENCE_USERNAME. Do not put secrets in command-line arguments or commit them to the repository. - TLS certificates are verified for every request. Private certificate authorities must be trusted by Python/Requests or supplied through a CA bundle; HTTP should be limited to isolated local testing.
- HTTP response bodies are not included in request exceptions.
- Matched values are omitted from reports unless
--show-secretsis enabled; source snippets and baseline files remain secret-safe in either mode. - Attachment downloads reject direct cross-origin URLs to avoid forwarding the Confluence authorization header to an unrelated host.
- Reports still contain page titles, URLs, attachment names, rule IDs, and fingerprints. Treat them as internal security data.
- Diagnostic logs omit credentials and content but retain internal URLs, space keys, content IDs, local paths, and operational counters. Protect them like scan reports.
- Fingerprints are hashes, not encryption. Low-entropy credentials may be guessable offline, so reports still need appropriate retention and access controls.
- A real finding should trigger credential revocation or rotation; removing it from Confluence alone is not sufficient.
Report security vulnerabilities privately as described in SECURITY.md. Never attach live credentials, internal scan results, or private Confluence content to a public issue.
Run the complete test suite:
python -m unittest discover -s tests -vOptional syntax compilation check:
python -m compileall -q confluleaks scanner testsThe tests cover configuration discovery, precedence, and validation; extraction; rules; context and entropy filtering; report redaction; history deduplication; comment scanning; attachment classification and limits; response/document/finding/runtime guardrails; regex timeouts; retry configuration; progress and diagnostic logging; private atomic report output; opt-in plaintext reports; insecure-HTTP confirmation; preflight compatibility checks; SARIF structure; baseline stability; and partial-error behavior.
Copyright 2026 wakeuppls.
Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
scanner/
auth.py Bearer and Basic authentication strategies
config.py YAML discovery, schema, and validation
preflight.py read-only connection and compatibility checks
progress.py elapsed progress output for long scans
logging_config.py rotating structured diagnostic logs
main.py CLI and exit-code policy
confluence.py REST client, pagination, retries, downloads
service.py scan orchestration and scope
extractor.py Confluence storage HTML to text
attachments.py attachment classification and decoding
detector.py rule matching and fingerprints
rules.py YAML rule loader and validation
default_rules.yaml built-in rule pack
models.py domain and report models
baseline.py baseline identity, loading, and atomic writing
output.py private atomic report-file output
report.py text and scanner-native JSON output
sarif.py SARIF 2.1.0 output
confluleaks/
__main__.py branded `python -m confluleaks` entry point
tests/ unit and integration-oriented tests
confluleaks.example.yaml safe, tracked configuration template
pyproject.toml Python build backend configuration
setup.cfg metadata, dependencies, package data, and console script
MANIFEST.in source-distribution inclusion and exclusion rules
LICENSE / NOTICE Apache-2.0 terms and attribution
SECURITY.md private vulnerability reporting policy
.github/workflows/ test and package-build CI
- compatibility testing against additional Confluence Data Center releases and Confluence Cloud;
- optional PDF and Office extraction without adding heavy dependencies to the base installation;
- modified-since or CQL-based incremental scans;
- optional OAuth or enterprise authentication adapters;
- issue-tracker integration;
- bounded concurrency and structured operational metrics.