Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
db60a24
R-22: apply Pint formatting across config, tests and workbench
belisarh Aug 27, 2026
f2117af
R-18: stop shipping the Tests\ namespace to consumers
belisarh Aug 27, 2026
e13de60
R-19: drop the unused Spatie dependency, declare the real ones
belisarh Aug 27, 2026
89f731f
R-23: fix licence filename and README metadata
belisarh Aug 27, 2026
f6b4e12
R-21: support PHP 8.5 and get the toolchain running on it
belisarh Aug 27, 2026
9f405cc
R-14: merge package config in register(), not boot()
belisarh Aug 27, 2026
c28883e
R-11: bind Redactor and Scanner as singletons
belisarh Aug 27, 2026
983aff0
R-20: remove the dead legacy API and stop reaching in via Reflection
belisarh Aug 27, 2026
e324f09
R-09: make the documented environment variables actually work
belisarh Aug 27, 2026
7a006f7
R-03: bound recursion depth and break reference cycles
belisarh Aug 27, 2026
27986a2
R-04: guarantee the logging path never throws
belisarh Aug 27, 2026
843b0c8
R-15: make PCRE failures fail closed instead of reading as "no match"
belisarh Aug 27, 2026
2aad15b
R-01: redact the sensitive span, not the whole value
belisarh Aug 27, 2026
786e227
R-02: make safe keys mean something, stop shipping PII as safe
belisarh Aug 27, 2026
12ddd9a
R-13: dispatch each node through the strategy chain exactly once
belisarh Aug 27, 2026
58bfde5
R-12: compile blocked-key patterns once instead of per key, per call
belisarh Aug 27, 2026
37080e1
R-08: implement the safe_keys wildcards the README documents
belisarh Aug 27, 2026
8a2c186
R-07: return redaction metadata alongside the payload, not inside it
belisarh Aug 27, 2026
20944be
R-16: measure entropy per character and per alphabet
belisarh Aug 27, 2026
16333af
R-17: validate the match before reporting it
belisarh Aug 27, 2026
728db28
R-06: redact logs with a Monolog processor, and stop dropping records
belisarh Aug 27, 2026
0fe7620
R-05: make the scanner's exclude patterns actually exclude
belisarh Aug 27, 2026
445b54e
R-10: give scan findings a rule, a location and an excerpt
belisarh Aug 27, 2026
47ea466
R-24: turn the test suite's reports into gates
belisarh Aug 27, 2026
a1fd1d0
docs: bring the README and CHANGELOG in line with the code
belisarh Aug 27, 2026
42da095
ci: fix the mutation job and unblock the Laravel 11 matrix legs
belisarh Aug 27, 2026
ed1d3f1
test: use a build-independent trigger for the PCRE failure tests
belisarh Aug 27, 2026
e572e73
test: stop timing assertions from running under coverage instrumentation
belisarh Aug 27, 2026
d5da1b5
ci: raise the coverage floor to 90 and scope the mutation run
belisarh Aug 27, 2026
d131d3c
test: pin every redaction threshold at its boundary
belisarh Aug 27, 2026
a07cf37
ci: report the mutation score instead of gating on it, for now
belisarh Aug 27, 2026
8986d3b
ci: reject multi-line commits and attribution trailers
belisarh Aug 27, 2026
0df1cb8
ci: run mutation testing locally only, not on pull requests
belisarh Aug 27, 2026
75b8113
deps: drop the unused pest-plugin-laravel dev dependency
belisarh Aug 27, 2026
f16d73a
deps: drop Laravel 11, support Laravel 12 and 13
belisarh Aug 27, 2026
5687855
feat: separate detection from operation, add deterministic pseudonymi…
belisarh Aug 27, 2026
65d501f
feat: compile path rules into a trie walked alongside the payload
belisarh Aug 27, 2026
5a6f347
feat: score detections and surface confidence through scan output
belisarh Aug 27, 2026
f255bc5
feat: scan files as overlapping line windows to keep memory flat
belisarh Aug 27, 2026
53988c6
feat: verify detected credentials behind three independent safety gates
belisarh Aug 27, 2026
f0a6113
docs: document paths, operators, pseudonymisation, confidence and ver…
belisarh Aug 27, 2026
7b253f2
perf: stop building scored detections twice per string value
belisarh Aug 27, 2026
1d76731
perf: hold compiled key matchers on the resolved profile
belisarh Aug 27, 2026
69e0692
perf: skip entropy analysis for values shorter than min_length
belisarh Aug 27, 2026
3f7be22
perf: tokenise without the /u modifier for ASCII values
belisarh Aug 27, 2026
dab23b7
fix: accept numeric path patterns, which PHP turns into int keys
belisarh Aug 27, 2026
f1ad45c
perf: return the original array when a subtree is unchanged
belisarh Aug 27, 2026
599f11b
perf: rewrite matched spans in one pass instead of splicing each
belisarh Aug 27, 2026
f382b26
fix: make operators.default reachable for pattern-detected values
belisarh Aug 27, 2026
24a5e7b
test: guard the hot-path shortcuts against being refactored away
belisarh Aug 27, 2026
2c40426
docs: record the hot-path work and the two defects it surfaced
belisarh Aug 27, 2026
9b4ec77
fix: track active objects by id to avoid PHP 8.5 deprecations
belisarh Sep 13, 2026
eb341bd
fix: pass throwables, dates, enums and closures through untouched
belisarh Sep 13, 2026
3d1fe32
feat: truncate long strings and scan the head instead of replacing them
belisarh Sep 13, 2026
b05adfa
docs: record opaque objects and long-string truncation
belisarh Sep 13, 2026
59d32cd
feat: collect detections and rewrite each value once
belisarh Sep 13, 2026
24a42c9
feat: route blocked-key values through operators by entity
belisarh Sep 13, 2026
7b3f8de
docs: describe the single detection pass and pattern keywords
belisarh Sep 13, 2026
1115887
feat: ship provider-key and safer identity patterns in every profile
belisarh Sep 13, 2026
89fa1b0
fix: share the pseudonymisation salt across profiles by default
belisarh Sep 13, 2026
5956b85
docs: record the shared pattern lists and the global salt
belisarh Sep 13, 2026
14189ee
feat: profile and per-rule allow-lists, and dictionary word rules
belisarh Sep 13, 2026
ba5e29e
perf: tokenise only candidates of min_length or longer for entropy
belisarh Sep 13, 2026
cfbc63c
docs: describe allow-lists, dictionary rules and the entropy tokeniser
belisarh Sep 13, 2026
b133006
feat: redact the application's own known secrets wherever they appear
belisarh Sep 13, 2026
45ecf52
feat: suppress a scan finding with a redactor:allow marker on the line
belisarh Sep 13, 2026
0c4195c
docs: describe known secrets and inline scan suppression
belisarh Sep 13, 2026
30a8be8
feat: named entity recognition through a Presidio-compatible recogniser
belisarh Sep 13, 2026
22a4d6f
docs: describe entity recognition and its gates
belisarh Sep 13, 2026
f2d11c5
fix: rebuild a cached profile when a known-secret source changes
belisarh Sep 13, 2026
93d695b
docs: coverage driver note, integration examples, coverage memory limit
belisarh Sep 13, 2026
9ccee7a
perf: resolve the pseudonymizer lazily, only for operators that need it
belisarh Sep 13, 2026
3a9794f
perf: skip rules by min_length and pre-check matches without captures
belisarh Sep 13, 2026
04965e3
docs: describe min_length and the hot-path work
belisarh Sep 13, 2026
5e0d712
perf: order rules by min_length and stop at the first too long to match
belisarh Sep 13, 2026
87aee45
perf: leave switched-off strategies out of the chain
belisarh Sep 13, 2026
645377d
perf: skip the operator machinery for a plain redaction
belisarh Sep 13, 2026
d96b572
test: time the performance guards as the best of several runs
belisarh Sep 13, 2026
561bc14
feat: scan staged, diff and history through git, with JUnit output
belisarh Sep 13, 2026
31c9a47
feat: publishable pre-commit hook and GitHub workflow for the scanner
belisarh Sep 13, 2026
e907b04
docs: describe change scanning, JUnit output and the CI stubs
belisarh Sep 13, 2026
3e5cd72
feat: scan inside base64, URL-encoded and JSON-escaped spans
belisarh Sep 13, 2026
3eb7872
docs: describe decoding in the scanner
belisarh Sep 13, 2026
917a2a8
feat: rules carry samples that redactor:validate proves
belisarh Sep 13, 2026
c6c2c5a
feat: report a ruleset fingerprint and warn on a stale baseline
belisarh Sep 13, 2026
a59d884
docs: describe rule samples and the ruleset fingerprint
belisarh Sep 13, 2026
b8ce067
feat: Redactor::fake() with leak assertions for application test suites
belisarh Sep 13, 2026
2ce8e25
docs: describe the test fake
belisarh Sep 13, 2026
aa6862d
feat: nullify operator, so a typed field stays a field
belisarh Sep 13, 2026
f83d957
feat: redact middleware for HTTP responses, profile per route
belisarh Sep 13, 2026
8594fb6
docs: describe the response middleware and nullify
belisarh Sep 13, 2026
3d05045
feat: redact streams chunk by chunk with a hold-back window
belisarh Sep 13, 2026
02c72db
docs: describe streaming redaction
belisarh Sep 13, 2026
c9f1955
feat: reversible tokens with a cache-backed, encrypted token store
belisarh Sep 13, 2026
e1b6118
test: time the path-rule guards as the best of several runs
belisarh Sep 13, 2026
6eaa5db
docs: describe reversible tokens
belisarh Sep 13, 2026
d020389
feat: dispatch RedactionPerformed with names and counts only
belisarh Sep 13, 2026
544e7a9
docs: describe the RedactionPerformed event
belisarh Sep 13, 2026
192e847
feat: redact MCP server responses and AI agent prompts
belisarh Sep 13, 2026
9662674
docs: describe the MCP and AI adapters
belisarh Sep 13, 2026
85d6b1c
refactor: open classes and use config(), event() and the container
belisarh Sep 13, 2026
991f570
refactor: shape the service provider like a first-party one
belisarh Sep 13, 2026
6cf9de9
refactor: throw package exceptions with bracketed values
belisarh Sep 13, 2026
3b748d8
refactor: name the strategy contract and formatter like first-party code
belisarh Sep 13, 2026
6e0674a
feat: fluent profile builder, inspect(), macros, array results
belisarh Sep 13, 2026
efb9c3c
docs: describe the first-party conventions pass
belisarh Sep 13, 2026
0fb4177
style: align docblocks and comments with the first-party register
belisarh Sep 13, 2026
4af2e9a
docs: merge duplicated changelog sections, use profiles() in validate
belisarh Sep 13, 2026
a76b31d
perf: read hot-path configuration through a memoised repository
belisarh Sep 13, 2026
f9c03ca
refactor: remove deprecated aliases and apply Rector across the tree
belisarh Sep 13, 2026
72f59cc
ci: Rector in preflight and CI, Semgrep and audit workflow
belisarh Sep 13, 2026
ae35dcb
test: cover every line, drop dead code, fix legacy-text sniffing
belisarh Sep 13, 2026
9891778
docs: structured documentation set, README as an index
belisarh Sep 13, 2026
49f3c0c
feat: only() and except() filter entities per redaction
belisarh Sep 14, 2026
6d7cf10
feat: national identifier and VAT validators, custom validators
belisarh Sep 14, 2026
2af2bd7
feat: region packs for GB, NL, DE, FR, IT, ES, BE, SE, NO, CA, AU, EU
belisarh Sep 14, 2026
4c8f09b
feat: OpenAI, Anthropic, SendGrid and Google verifiers, register()
belisarh Sep 14, 2026
6c4d356
feat: recognise every prose value in one call before the walk
belisarh Sep 14, 2026
c9b607d
docs: point entity recognition at the redactor-onnx companion package
belisarh Sep 14, 2026
6321feb
docs: 1.0.0 changelog heading and companion package pointers
belisarh Sep 14, 2026
8754d48
ci: exclude the Stripe secret rule that fires on the shipped samples
belisarh Sep 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
* text=auto eol=lf

# Keep development-only material out of the distributed package.
/.github export-ignore
/.githooks export-ignore
/scripts export-ignore
/tests export-ignore
/workbench export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/pint.json export-ignore
/testbench.yaml export-ignore

# Diff/linguist hints
*.php diff=php
/tests/** linguist-vendored
64 changes: 64 additions & 0 deletions .githooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash
#
# Enforce single-line commit messages with no attribution trailers.
#
# Repository reporting and per-developer exposure coverage are derived from git
# history. Co-author trailers split authorship across two identities and skew
# those reports; multi-line bodies are noise the parser has to strip. The "why"
# belongs in the PR description, the CHANGELOG, or a comment at the code site,
# where it stays readable.
#
# Installed by `composer setup-hooks`, which points core.hooksPath at .githooks.

MSG_FILE="$1"
SOURCE="$2"

# Merges, squashes and reverts generate bodies git wrote itself.
case "$SOURCE" in
merge|squash) exit 0 ;;
esac

# Strip comments and trailing blank lines; that is what git will actually store.
BODY="$(grep -v '^#' "$MSG_FILE" | sed -e :a -e '/^\s*$/{$d;N;ba' -e '}')"

if [[ -z "${BODY//[[:space:]]/}" ]]; then
# An empty message aborts the commit anyway; let git say so.
exit 0
fi

FIRST_LINE="$(printf '%s\n' "$BODY" | head -n 1)"

case "$FIRST_LINE" in
Revert\ \"*) exit 0 ;;
esac

fail() {
echo ""
echo "🚫 Commit rejected: $1"
echo ""
echo " Commit messages must be a single line, with no body and no trailers."
echo " Put the reasoning in the PR description, the CHANGELOG, or a code comment."
echo ""
echo " Got:"
printf '%s\n' "$BODY" | sed 's/^/ | /'
echo ""
exit 1
}

if printf '%s\n' "$BODY" | grep -qiE '^[[:space:]]*(co-authored-by|claude-session|signed-off-by[[:space:]]*:[[:space:]]*claude)'; then
fail "attribution trailers break authorship reporting."
fi

if printf '%s\n' "$BODY" | grep -qiE 'claude\.ai/code/session'; then
fail "session links do not belong in git history."
fi

if [[ "$(printf '%s\n' "$BODY" | wc -l | tr -d ' ')" -gt 1 ]]; then
fail "the message has more than one line."
fi

if [[ ${#FIRST_LINE} -gt 72 ]]; then
fail "the subject is ${#FIRST_LINE} characters; keep it to 72."
fi

exit 0
8 changes: 7 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ updates:
schedule:
interval: "weekly"
labels:
- "dependencies"
- "dependencies"
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
44 changes: 35 additions & 9 deletions .github/workflows/php-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@ jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
# One unusable matrix leg should not cancel the other nine.
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.3, 8.4]
laravel: [11.*, 12.*]
php: ['8.3', '8.4', '8.5']
laravel: [12.*, 13.*]
stability: [prefer-lowest, prefer-stable]
include:
- laravel: 11.*
testbench: ^9.9
carbon: ^2.63
- laravel: 12.*
testbench: 10.*
carbon: ^2.63|^3.0
- laravel: 13.*
testbench: 11.*

name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}

Expand All @@ -42,13 +41,40 @@ jobs:
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

# Carbon is not pinned here: the package only reaches it through
# Laravel's helpers, so whatever the framework resolves is the version
# worth testing against.
- name: Install dependencies
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.stability }} --prefer-dist --no-interaction

- name: List Installed Dependencies
run: composer show -D

- name: Execute tests
run: vendor/bin/pest --ci --bail --compact --memory --coverage
run: vendor/bin/pest --ci --compact --memory --coverage --min=100

performance:
name: performance guards
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, intl, fileinfo
# Explicitly no coverage: instrumentation dominates the clock and
# flattens the difference between a fast and a slow implementation,
# so these assertions skip themselves when a driver is active.
coverage: none

- name: Install composer dependencies
uses: ramsey/composer-install@v3

- name: Run performance guards
run: vendor/bin/pest --testsuite=Performance --ci
61 changes: 61 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Security

# SAST and dependency advisories, on a schedule as well as on push so an
# advisory published while nobody is committing is still caught.
on:
push:
branches: [main]
pull_request:
schedule:
- cron: '30 5 * * 1'
workflow_dispatch:

permissions:
contents: read

jobs:
semgrep:
name: Semgrep SAST
runs-on: ubuntu-latest
timeout-minutes: 10
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

# The package's own rule samples and test fixtures are credential-shaped
# by design; the Stripe sample is Stripe's public documentation key.
# That rule is excluded rather than the files, so every other secret
# rule still runs over them.
- name: Semgrep scan
run: >
semgrep scan
--config p/php
--config p/secrets
--exclude-rule generic.secrets.security.detected-stripe-api-key.detected-stripe-api-key
--error
--text

audit:
name: Dependency advisories
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
tools: composer:v2
coverage: none

- name: Install dependencies
run: composer install --no-interaction --prefer-dist --no-progress

- name: Composer audit
run: composer audit
19 changes: 15 additions & 4 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ on:
push:
paths:
- '**.php'
- 'composer.lock'
- 'composer.json'
- 'phpstan.neon.dist'
- '.github/workflows/phpstan.yml'
- 'rector.php'
- '.github/workflows/static-analysis.yml'
pull_request:
paths:
- '**.php'
- 'composer.json'
- 'phpstan.neon.dist'
- 'rector.php'
- '.github/workflows/static-analysis.yml'

jobs:
phpstan:
Expand All @@ -19,12 +27,15 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
php-version: '8.5'
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, swoole, openssl
coverage: none

- name: Install composer dependencies
uses: ramsey/composer-install@v3

- name: Run Rector (dry run)
run: ./vendor/bin/rector process --dry-run --no-progress-bar

- name: Run PHPStan
run: ./vendor/bin/phpstan --error-format=github
run: ./vendor/bin/phpstan analyse --error-format=github --no-progress --memory-limit=1G
6 changes: 2 additions & 4 deletions .github/workflows/style-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: Code Style

on:
workflow_dispatch:
pull_request:
push:
branches-ignore:
- 'dependabot/npm_and_yarn/*'
Expand All @@ -14,14 +15,11 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.3
php-version: '8.5'

- name: Checkout
uses: actions/checkout@v4

- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"

- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ vendor
composer.lock
node_modules
build
.phpunit.cache
infection.log
.pint.cache
.idea
.DS_Store
Expand Down
7 changes: 7 additions & 0 deletions .semgrepignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Dependencies and generated output; findings there belong upstream.
vendor/
node_modules/
.phpunit.cache/

# Test fixtures hold deliberately fake credentials the scanner must find.
tests/Feature/fixtures/
Loading
Loading