Skip to content

fix(security): keep UUIDs intact through Sanitizer phone redaction - #54

Merged
minerva-sky merged 1 commit into
codenamev:mainfrom
minerva-sky:fix/sanitizer-uuid-guard
Oct 30, 2026
Merged

minerva-sky merged 1 commit into
codenamev:mainfrom
minerva-sky:fix/sanitizer-uuid-guard

Conversation

@minerva-sky

@minerva-sky minerva-sky commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The Ruby 3.2.4 CI job goes red at random on #40, #50, #51 and #53 — always the same example, plan_orchestrator_spec "skips transitively through the graph", with output like:

expected: "73fb9a6e-9973-4748-87aa-3aeaa0c29af2"
     got: "73fb9a6e-[REDACTED_PHONE]-87aa-3aeaa0c29af2"

Not a Ruby/Linux/ordering issue — the mask is produced inside the object under test. TaskFailure#initialize sanitizes message and context through Security::Sanitizer, whose second phone regex matches any UUID whose 2nd + 3rd groups are all digits (9973-4748). About 4% of SecureRandom.uuid values qualify, so every spec that asserts on a dependency_id or an id inside a failure message flakes at that rate. Local runs just got lucky.

Fix

  • Sanitizer#sanitize_string masks UUID spans with placeholders before the PII patterns run and restores them afterwards (PROTECTED_SPANS). Fixes message and context together.
  • Regression spec: a digit-heavy UUID next to a real phone number — the UUID survives, the phone is still redacted, hash values too.

Probe: 2000 random UUIDs through the sanitizer → 0 mangled (was ~4%).

Verified under ruby 3.2.4, --order defined: sanitizer + task_failure + plan_orchestrator specs, 57 examples, 0 failures; standardrb clean.

Once merged, rebasing #40/#50/#51/#53 (or just re-running their CI after main moves) should turn the 3.2.4 job green.

🤖 Generated with Claude Code

Closes #41.

TaskFailure#initialize runs message and context through Security::Sanitizer.
The loose phone pattern matched any UUID whose 2nd and 3rd groups are all
digits (~4% of SecureRandom.uuid values), turning e.g.
73fb9a6e-9973-4748-87aa-… into 73fb9a6e-[REDACTED_PHONE]-87aa-… and
corrupting dependency_id / task ids. This is the random red on the Ruby
3.2.4 CI job for codenamev#40, codenamev#50, codenamev#51 and codenamev#53 (plan_orchestrator_spec 'skips
transitively through the graph').

Mask UUID spans with placeholders before applying PII patterns and restore
them after; add a regression spec with a digit-heavy UUID.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@minerva-sky

Copy link
Copy Markdown
Collaborator Author

CI evidence from the deps loop (2026-10-13): this flake is no longer a 10% thing, it is red on every run since #34 added the transitive-skip spec.

Each of those specs now mints several UUIDs per example, so the odds of at least one carrying a phone-shaped run went from ~10% per UUID to roughly a coin flip per run. Nothing in #27 or #29 touches the orchestrator; this PR is what turns them green.

@minerva-sky
minerva-sky merged commit 5d2790f into codenamev:main Oct 30, 2026
1 check passed
minerva-sky added a commit that referenced this pull request Oct 31, 2026
…ile (#42) (#83)

* deps: raise required_ruby_version to >= 3.2, test a real Ruby matrix (#15)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: add benchmark gem (bundled gem since Ruby 4.0; spec/performance requires it)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Bound the six unconstrained runtime dependencies; lock ruby-openai 8.3.0

ruby-openai gets a range (>= 7.1, < 9) because 7.x and 8.x both pass the
suite and a pessimistic ~> 7.1 would strand consumers already resolving
8.x. dry-schema, zeitwerk, ostruct, logger and cgi get ~> at their locked
minors. Adding constraints where none existed is semver-visible, so this
should ship as a minor bump (CHANGELOG under Unreleased).

Closes #16.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* deps: refresh the dev lockfile within its existing constraints

`bundle update` honouring every cap already in the Gemfile moves 51 gems:
json 2.7 -> 3.1, zeitwerk 2.6 -> 2.8, dry-schema 1.13 -> 1.16 (dry-core
1.0 -> 1.2, dry-types 1.7 -> 1.9), async 2.24 -> 2.37 (the Gemfile cap the
lock never reached), thor 1.3 -> 1.5, standard 1.32 -> 1.57 (rubocop 1.91),
rspec 3.13.0 -> 3.13.2, webmock 3.23 -> 3.34. These are the versions a
fresh `gem install agentic` already resolves to; CI was testing the old
numbers.

The refresh drops the orphan base64 entry that vcr 6.3 pulled in, which is
the only thing that let authentication_system.rb's dead `require "base64"`
load on Ruby >= 3.4 (base64 is a bundled gem there). The require goes; the
file never calls Base64.

Suite on the refreshed lock (Ruby 4.0.2): 1391 examples, the two
pre-existing flakes only (plan_orchestrator_spec:243 UUID redaction, #54;
event_dispatcher_integration_spec:291 wall clock, #36). standardrb 1.57
reports no offences.

Closes #64.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* deps: cap parallel and simplecov for the Ruby 3.2.4 CI floor

The refresh resolved on Ruby 4.0.2 and let parallel 2.3.0 (via rubocop
1.91) and simplecov 1.3.2 into the lock; both declare
required_ruby_version >= 3.3, so CI's frozen install on 3.2.4 would fail
before a spec ran. Same cap pattern as async/console/dry-configurable/
io-event: parallel < 2 (1.28.0, floor 2.7), simplecov < 1.3 (1.2.0, floor
3.2). Every locked spec now satisfies 3.2.4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* deps: move the Ruby 3.2 caps out of the Gemfile into a floor-leg gemfile (#42)

The shared Gemfile caps pinned every CI leg to the 3.2-era dependency set,
so the 3.3/3.4/latest legs never exercised the async a consumer on a
maintained Ruby installs. The 3.2 leg now loads gemfiles/ruby-3.2.gemfile,
which gets its own lockfile resolved on 3.2; Bundler's required_ruby_version
check selects the same versions the caps did, so the file carries none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* deps: commit the 3.2 leg's lockfile and let Dependabot move it

Panel revision: an unlocked floor leg re-resolved against live rubygems on
every run, so an upstream release could turn it red with no commit and no
Dependabot PR to bisect against. The floor gemfile moves to
gemfiles/ruby-3.2/Gemfile (Dependabot only discovers files named Gemfile),
its lockfile is committed, a ruby '< 3.3' line keeps Dependabot inside 3.2,
and dependabot.yml gains a bundler entry for the directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: audit the Ruby 3.2 lockfile; document regenerating it

Panel notes on the revision: audit.yml's paths filter and check covered only
the root Gemfile.lock, and a root Gemfile change now needs the floor lockfile
re-resolved on 3.2 or that leg fails its frozen install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Valentino Stoll <v@codenamev.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PII sanitizer rewrites UUIDs: ~10% of task IDs in TaskFailure records come back as [REDACTED_PHONE]

2 participants