Skip to content

feat(resume): commit redacted client names so builds need no sops key - #143

Merged
roschaefer merged 2 commits into
mainfrom
redact-clients
Oct 1, 2026
Merged

roschaefer merged 2 commits into
mainfrom
redact-clients

Conversation

@roschaefer

@roschaefer roschaefer commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

CI and the Netlify build needed SOPS_AGE_KEY only to decrypt the client names and redact them right away. Netlify exposes build-scoped variables to the whole build, including its automatic pnpm install, so the key could not be kept away from dependency code there.

pnpm redact-clients now decrypts locally and writes the redacted names as plain fields next to the encrypted ones. Normal builds read those, and only RESUME_MODE=unredacted still calls sops. It has to be run after editing encrypted fields; a build fails if an entry has encrypted fields but no redacted ones, which catches a forgotten run for a new client, but not a renamed one.

The code used "masked" and "redacted" for the same thing, and the resume modes even paired "masked" with "unredacted". The data (redacted: true), the redacted-client pages and RESUME_MODE=unredacted already said "redacted", so that term stays.

Redacted and unredacted .generated output is byte-identical to before. Verified with pnpm check:quick and a Netlify-like build in a node:26 container without any key.

Closes #141

CI and the Netlify build needed SOPS_AGE_KEY only to decrypt the client
names and mask them right away. Netlify exposes build-scoped variables
to the whole build, including its automatic `pnpm install`, so the key
could not be kept away from dependency code there.

`pnpm mask-clients` now decrypts locally and writes the masked names as
plain fields next to the encrypted ones. Normal builds read those, and
only RESUME_MODE=unredacted still calls sops. It has to be run after
editing encrypted fields; a build fails if an entry has encrypted fields
but no masked ones, which catches a forgotten run for a new client, but
not a renamed one.

linkedin-sync reads the decrypted file and preferred `entity` over
`sopsEncryptedEntity`, so it would now have compared LinkedIn against
the masked names. It prefers the decrypted name instead.

Masked and unredacted .generated output and linkedin-sync's resume
output are byte-identical to before. Verified with `pnpm check:quick`
and a Netlify-like build in a node:26 container without any key.

Closes #141
The code used "masked" and "redacted" for the same thing, and the
resume modes even paired "masked" with "unredacted". The data
(`redacted: true`), the redacted-client pages and RESUME_MODE=unredacted
already said "redacted", so that term stays: RESUME_MODE=redacted,
`pnpm redact-clients`, and `redactEntity`.
@netlify

netlify Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for roschaefer ready!

Name Link
🔨 Latest commit bcacf80
🔍 Latest deploy log https://app.netlify.com/projects/roschaefer/deploys/6abe5b1a764fbb0008b20933
😎 Deploy Preview https://deploy-preview-143--roschaefer.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 791f1ce5-64b8-482b-abb1-db21ea6cbf2a

📥 Commits

Reviewing files that changed from the base of the PR and between 258d46b and bcacf80.

📒 Files selected for processing (15)
  • .github/workflows/ci.yml
  • AGENTS.md
  • README.md
  • netlify.toml
  • package.json
  • resume.i18n.json
  • scripts/build-typst-data.ts
  • scripts/generate-resume-source.ts
  • scripts/netlify-build.sh
  • scripts/redact-clients.ts
  • src/lib/utils/derive-resume.ts
  • src/lib/utils/redact-entity.test.ts
  • src/lib/utils/redact-entity.ts
  • src/lib/utils/resolve-sops-fields.test.ts
  • src/lib/utils/resolve-sops-fields.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Resume builds now use committed redacted client fields by default. A new command generates those fields from encrypted data. Unredacted generation still decrypts the source. CI and Netlify build configuration no longer require the SOPS key.

Changes

Resume redaction and generation

Layer / File(s) Summary
Prepare and commit redacted fields
src/lib/utils/resolve-sops-fields.ts, src/lib/utils/redact-entity.ts, scripts/redact-clients.ts, package.json, resume.i18n.json, src/lib/utils/*test.ts
The redaction helper adds redacted plain fields and markers to encrypted data. The new redact-clients command writes the result to the resume source. Changed entries now include committed redacted entity values.
Resolve fields during resume generation
scripts/generate-resume-source.ts, src/lib/utils/resolve-sops-fields.ts, src/lib/utils/resolve-sops-fields.test.ts, scripts/build-typst-data.ts, src/lib/utils/derive-resume.ts
Redacted mode uses committed fields and fails when encrypted fields lack a redaction marker. Unredacted mode decrypts the source and restores real values. Tests cover both modes and missing markers.
Update build wiring and guidance
.github/workflows/ci.yml, scripts/netlify-build.sh, netlify.toml, README.md, AGENTS.md
CI and Netlify build scripts no longer pass or restore SOPS_AGE_KEY. The Netlify tool setup and project guidance describe the redacted-field workflow.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Build
  participant generate as generate-resume-source.ts
  participant source as resume.i18n.json
  participant resolver as resolveSopsEncryptedFields
  participant output as generated resume source
  Build->>generate: run with default redacted mode
  generate->>source: read committed redacted fields
  generate->>resolver: resolve redacted fields
  resolver-->>generate: return resume data without encrypted fields
  generate-->>output: write resolved source
Loading

Merge Risk: ⚪ Minimal · up to bcacf

Default builds now use committed redacted client names and no longer need the SOPS key. Tests cover the new behavior, and no concrete merge-blocking issue was found.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to bcacf

Keyless builds reduce routine access to private client identities. However, hosted build setup no longer strips an inherited decryption key before downloading and running tooling. If a previously configured key remains, its exposure expands; removal of that hosted secret has not been confirmed.

Retained concerns

  • Medium · security · inferred: If the existing Netlify decryption key remains configured, removing environment isolation newly exposes it to downloaded bootstrap tooling before the site build. Provider-side retirement of that key is unverified.
Security review details

Security Blast Radius

  • inferred — A compromised bootstrap process that receives a legacy SOPS key could use its decryption authority against the encrypted client fields in this resume source. The demonstrated data scope is the complete encrypted resume source, not merely one redacted field; broader credential reuse is not established.

Security Findings and Attack Paths

  • inferred — The conditional attack path is compromise of downloaded bootstrap tooling followed by access to an inherited SOPS_AGE_KEY. Head removes the prior isolation for that phase. Earlier Netlify dependency-installation exposure predates this change, and no evidence confirms that the hosted key remains configured or has been accessed.

Trust Boundaries and Controls

  • observed — Default generation no longer needs decryption authority and drops recognized encrypted fields from output. It instead trusts committed plaintext shadows and their redacted marker. Explicit unredacted mode remains environment-selected, as before; its local-only restriction is documented but not enforced by a deployment-specific guard.

Hardening Proposals

  • proposed — Retire hosted decryption secrets as part of the keyless-build transition and retain defensive key stripping before downloaded tooling runs. A hosted-build guard rejecting unredacted mode would additionally enforce the existing local-only policy.
  • proposed — For safer ownership of the encrypted source, refresh from one consistent snapshot and validate a temporary result before atomically replacing the file. This would limit concurrent-edit loss and interruption damage without giving ordinary builds decryption credentials.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 9…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: committing redacted client names so normal builds no longer require SOPS_AGE_KEY.
Description check ✅ Passed The description accurately explains the redaction workflow, build behavior, terminology changes, validation, and limitation for renamed clients.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

@roschaefer roschaefer changed the title Redact clients feat(resume): commit redacted client names so builds need no sops key Oct 1, 2026
@roschaefer
roschaefer marked this pull request as ready for review October 1, 2026 13:27
@roschaefer
roschaefer merged commit 507571f into main Oct 1, 2026
9 checks passed
@roschaefer
roschaefer deleted the redact-clients branch October 1, 2026 14:02
roschaefer added a commit that referenced this pull request Oct 1, 2026
507571f feat(resume): commit redacted client names so builds need no sops key (#143)

git-subtree-dir: roschaefer.de
git-subtree-split: 507571f
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.

Commit masked client names so builds no longer need sops

1 participant