Withhold an address wherever it is written, and never from cache - #47
Merged
Conversation
The previous fix withheld an address in the preamble and shipped. The live resume that prompted it carried the address twice: once under the name, and once in a section body reading "Full-time autonomous. Contact: <address>". The first was withheld, the second went out to every signed-out reader, and production still served it after the deploy. That was a fix shaped like the example rather than like the problem. A section body is not a special case to enumerate: the rule this function exists to enforce is that a signed-out reader does not get a contact channel, and an address is a contact channel wherever it appears. Every line is checked now. The check is an unconditional `replace` rather than `test` then `replace`. The pattern is global, and a global regex's `test` advances lastIndex between calls, so it reports false for matches it has already walked past. That drops roughly every other address in a run of them while a single-line unit test passes, which is worse than not redacting at all because it looks fixed. The candidate directory leaked the same address a second way. `parsed` is a cache written on save, so correcting the parser does nothing for rows already in the table, and the live listing kept its old headline -- markup, address and all -- out of correct code reading a stale value. A backfill would repair those rows without stopping the next one: any resume saved by an older build, restored from a backup, or written straight into the column arrives the same way. The headline is one line of text on a public page, so it is re-checked on the way out, where it cannot go stale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016DEUXec5um4FY3EbqfwtYm
ralyodio
added a commit
that referenced
this pull request
Sep 10, 2026
Minor rather than patch because commands and endpoints were added, and because the reason to cut this at all is that /docs is currently ahead of the published client: it tells people to run `agenticjobs employer create` and `agenticjobs resume publish`, and npm still ships 0.10.0, where neither exists. Since 0.10.1: - Employers and resume visibility as CRUD (#48). employer list/show/create/update/delete, resume publish/unpublish/visibility/ delete and --visibility on save and import, with PATCH and DELETE /api/v1/orgs/{slug} behind them. A rename keeps the slug; an employer that has published cannot be deleted. - Getting listed and hiring are documented at all (#45), and swarm capacity on a candidate profile (#46). - An address is withheld wherever it is written, and never from cache (#47). - Boolean CLI flags no longer swallow the command after them (#49). - Salary bounds read correctly in summaries and compare correctly across periods in search (#50, #51). - A JSON parse failure on a 200 is reported rather than swallowed (#52). - Job and DOCX imports keep titles, structure, tabs and line breaks (#53, #54). Both version files move together: package.json and VERSION in src/config.ts. Claude-Session: https://claude.ai/code/session_01D8ai32QMBsKEs1nigijSiJ Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #46, which fixed half of this and shipped. Verified against production after that deploy: the address was still public, two ways.
1. An address in a section body
The live resume carried it twice:
#46 withheld the preamble line. The second went out to every signed-out reader and to all four download formats, which is the same leak through a different line.
That was a fix shaped like the example rather than like the problem. A section body is not a special case to enumerate — the rule this function exists to enforce is that a signed-out reader does not get a contact channel, and an address is a contact channel wherever it is written. Every line is checked now.
The global-regex trap
The check is an unconditional
replace, nevertestthenreplace. The pattern is global, and a global regex'stestadvanceslastIndexbetween calls, so it reportsfalsefor matches it has already walked past. In a run of addresses that drops roughly every other one — while a single-line unit test passes happily. That is worse than not redacting at all, because it looks fixed. There is a test with ten consecutive addresses specifically for it.2. The stale
parsedcacheparsedis a JSONB cache written on save, so correcting the parser does nothing for rows already in the table. The candidate directory kept serving the old headline — markup and address,Operated by:** DevilX (<address>)— out of correct code reading a stale value. That is the shape of every derived-cache bug: the fix is right and the data is old.A backfill would repair those rows but would not stop the next one: any resume saved by an older build, restored from a backup, or written straight into the column arrives the same way. The headline is one line of text on a public directory page, so it is re-checked on the way out, where it cannot go stale. Stray markup is cleaned; an address drops the headline entirely.
Tests
test/redaction.test.ts, 7 cases: an address in a section body, several on one line, ten consecutive lines (thelastIndexcase), a clean resume left byte-identical, and both cached-headline paths.Full suite: 145 passing, typecheck clean.
Note
agenticjobsproduction does auto-deploy from GitHub now — a build started two seconds after #46 merged, with a changed image digest. My note saying deploys here are manual (source.repo: None) is out of date; I have corrected it.🤖 Generated with Claude Code
https://claude.ai/code/session_016DEUXec5um4FY3EbqfwtYm