Skip to content

feat(utils): linkAuth pure engine and macro vocabulary (#113 slice 1)#120

Merged
ejdutton merged 2 commits into
mainfrom
feat/113-linkauth-engine
Jun 5, 2026
Merged

feat(utils): linkAuth pure engine and macro vocabulary (#113 slice 1)#120
ejdutton merged 2 commits into
mainfrom
feat/113-linkauth-engine

Conversation

@ejdutton

@ejdutton ejdutton commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Slice 1 of issue #113 (authenticated external link resolution). Adds the pure-logic foundation — config-driven engine + macros + Zod schema — with no consumer wiring yet. The engine resolves an authenticated fetch plan for a URL but doesn't issue requests, doesn't touch the existing ExternalLinkValidator, and doesn't add codes to CODE_REGISTRY. Those land in slice 2.

What's in this PR

Engine — eight modules in packages/utils/src/link-auth/:

Module Role
transforms.ts Closed allowlist: base64url, urlencode, lower
template.ts ${name} / ${transform(name)} renderer
rewrite.ts Ordered when → vars → to pipeline
build-headers.ts Header rendering + structural Authorization redaction
select-provider.ts Host-glob match with excludeHost (picomatch)
expand-macro.ts YAML loader + deep-merge expander (lazy load)
resolve-token.ts Ordered env / safeExecResult-backed command sources
resolve.ts Public resolveAuthenticatedUrl(url, config) entry

Returns one of:

  • { fetchUrl, headers } — ready to fetch
  • { outcome: 'unsupported' } — no provider claims, or rewrite missed
  • { outcome: 'unverified', reason } — claimed + rewrote, but no token resolved

Macrosgithub and sharepoint ship as a YAML data asset (src/link-auth/macros.yaml). New cross-platform post-build asset-copy in packages/dev-tools/src/copy-yaml-assets.ts invoked from utils' build script.

SchemaLinkAuthConfigSchema (Zod, strict) in packages/resources/src/schemas/link-auth.ts. Wired as linkAuth: LinkAuthConfigSchema.optional() on ResourcesConfigSchema. Accepts either { use: <macro>, ...overrides } or full inline providers.

Tests — 140 unit tests in utils + 29 schema tests in resources, all pure-logic (no network, no filesystem dependencies except the loaded macros.yaml asset). Security-load-bearing tests pin:

  • Closed-allowlist guarantee against Object.prototype keys (toString, __proto__, constructor, etc.)
  • ${__proto__} lookup bypass defense in the template renderer
  • Token never leaks into Authorization header (precedence rule when a regex capture is named token)
  • shell: false literal-argv handling in token commands (shell operators become literal argv elements, not pipes)
  • RFC 4648 §10 known-answer vectors for base64url (guards against lockstep encoder/decoder regression)

What's NOT in this PR (deferred per design)

  • Wiring into ExternalLinkValidator health-check branch — slice 2
  • The five LINK_AUTH_* entries in CODE_REGISTRY + docs/validation-codes.mdslice 2
  • The doc-anchor coverage test for CODE_REGISTRY entries — slice 2 (will audit existing codes for gaps when added)
  • Content-fetch primitive + content cache — slice 3
  • Cross-platform .cmd-shim system test for token command dispatch — slice 4
  • GIT_* env scrubbing for git-backed token commands — slice 4

Notable design decisions

  • Lazy macro loading. expand-macro.ts defers readFileSync to first call. Why: other tests in the repo mock node:fs, so eager module-init reads crash unrelated test suites. Module import is now side-effect-free.
  • Object.create(null) everywhere. Every map produced by the engine (contexts, captures, headers, expanded macros) uses null prototypes. Belt-and-braces against __proto__-shaped keys from any source (YAML, regex captures, user config).
  • The Provider type lives in resolve.ts, the schema in resources. Two sources of truth for the shape, kept in sync by code review. Future option to derive one from the other (probably the TS interface from the Zod schema), but not in this slice.
  • use: form overrides are NOT strictly validated. Macro overrides can be any shape per Zod's .passthrough(). Strict validation of the merged result is post-expansion (slice 2 wires that).

Test plan

  • bun run validate passes locally (✅ confirmed: 14 steps, 214s)
  • CI green
  • Spot-check that resources.linkAuth: { providers: [{ use: 'github' }] } in a vibe-agent-toolkit.config.yaml parses without error (manual)
  • Spot-check that expandMacro('github') returns the expected shape via a Node REPL (manual)

Implements design doc in #113 (slice 1 of 4).

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.17647% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.78%. Comparing base (2050b5b) to head (268770f).

Files with missing lines Patch % Lines
packages/utils/src/link-auth/resolve-token.ts 82.92% 7 Missing ⚠️
packages/utils/src/link-auth/expand-macro.ts 96.15% 3 Missing ⚠️
packages/utils/src/link-auth/template.ts 94.91% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #120      +/-   ##
==========================================
+ Coverage   81.49%   81.78%   +0.29%     
==========================================
  Files         215      223       +8     
  Lines       16795    17135     +340     
  Branches     3214     3326     +112     
==========================================
+ Hits        13687    14014     +327     
- Misses       3108     3121      +13     
Files with missing lines Coverage Δ
packages/utils/src/link-auth/build-headers.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/resolve.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/rewrite.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/select-provider.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/transforms.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/expand-macro.ts 96.15% <96.15%> (ø)
packages/utils/src/link-auth/template.ts 94.91% <94.91%> (ø)
packages/utils/src/link-auth/resolve-token.ts 82.92% <82.92%> (ø)
Files with missing lines Coverage Δ
packages/utils/src/link-auth/build-headers.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/resolve.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/rewrite.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/select-provider.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/transforms.ts 100.00% <100.00%> (ø)
packages/utils/src/link-auth/expand-macro.ts 96.15% <96.15%> (ø)
packages/utils/src/link-auth/template.ts 94.91% <94.91%> (ø)
packages/utils/src/link-auth/resolve-token.ts 82.92% <82.92%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jdutton jdutton left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve. High-quality, security-conscious foundation slice. Read all 8 engine modules, the schema, build helper, and wiring; ran the 169 isolated unit tests (all green); and independently verified the security-load-bearing properties.

Verified independently

  • 169 unit tests pass (140 utils + 29 resources).
  • Host-match anchoring is real — ran picomatch directly: github.com rejects evil-github.com and github.com.evil.com; *.sharepoint.com claims subdomains but not the bare apex.
  • safeExecResult is shell: false — the §6.1 "shell operators become literal argv" claim holds at the exec layer.
  • Token-never-leaks invariant in resolve.ts is correct — resolved token wins over any regex capture named token.

Strengths

  • Consistent prototype-pollution discipline (Object.create(null) + Object.hasOwn everywhere; closed allowlist via hasOwn, not in), each pinned by adversarial tests.
  • Clean, genuinely-pure slice boundary; thoughtful lazy macro load.
  • Postel's law applied correctly (strict inline schema, .passthrough() only on the macro-override branch).

Minor (non-blocking)

  1. Comment rotexpand-macro.ts:10 and the CHANGELOG entry reference scripts/copy-yaml-assets.mjs, which doesn't exist; the real helper is packages/dev-tools/src/copy-yaml-assets.ts. Worth fixing both (CHANGELOG is still in [Unreleased]).
  2. Test gapdeepMerge is safe against a __proto__-as-override-key attack, but unlike every sibling module it has no adversarial test pinning it. Cheap to add (expandMacro('github', { __proto__: { polluted: true } }) → assert no global pollution).
  3. Informational: redactHeaders is implemented/tested but has no production call site until slice 2 — the §8 "tokens never leak" guarantee depends on that wiring landing.

Nice work.

Adds the pure-logic foundation for the linkAuth feature designed in issue
#113: a config-driven engine that resolves authenticated external URLs by
rewriting them to authenticatable endpoints and attaching tokens, with
neither network nor filesystem dependencies in the engine itself.

Eight modules under `packages/utils/src/link-auth/`:
  - `transforms.ts`     closed allowlist (base64url, urlencode, lower)
  - `template.ts`       tiny `${name}` / `${transform(name)}` renderer
  - `rewrite.ts`        ordered when → vars → to pipeline
  - `build-headers.ts`  header rendering + structural Authorization redaction
  - `select-provider.ts` host-glob match with excludeHost (picomatch)
  - `expand-macro.ts`   YAML loader + deep-merge expander (lazy load)
  - `resolve-token.ts`  ordered env / safeExecResult-backed command sources
  - `resolve.ts`        public `resolveAuthenticatedUrl(url, config)` entry

Ships `github` and `sharepoint` macros as a YAML data asset
(`src/link-auth/macros.yaml`). New cross-platform post-build asset-copy
lives in `packages/dev-tools/src/copy-yaml-assets.ts` and is invoked from
`packages/utils/package.json`'s build script — first YAML-data-shipping
pattern in the utils package.

Companion Zod schema in `@vibe-agent-toolkit/resources`
(`src/schemas/link-auth.ts`) validates the `resources.linkAuth` config
block (strict; accepts either `{ use: <macro>, ...overrides }` or a full
inline provider). Wired as an optional field on `ResourcesConfigSchema`.

140 unit tests in utils + 29 schema tests in resources, all pure-logic,
no network or filesystem dependencies. Security-load-bearing tests pin the
closed-allowlist guarantee, the `${__proto__}` lookup bypass defense, the
token-never-leaks-into-Authorization invariant (precedence rule when a
regex capture is named `token`), and `shell: false` literal-argv handling
in token commands.

Out of scope for this slice (deferred to subsequent slices per the design's
non-goals): wiring into `ExternalLinkValidator` (slice 2), the five
`LINK_AUTH_*` `CODE_REGISTRY` entries (slice 2), the content-fetch primitive
+ content cache (slice 3), and the cross-platform `.cmd`-shim system test
(slice 4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ejdutton ejdutton force-pushed the feat/113-linkauth-engine branch from 5046f52 to 6e0b064 Compare June 5, 2026 15:54
@ejdutton ejdutton marked this pull request as ready for review June 5, 2026 16:24
- Fix stale path reference in expand-macro.ts JSDoc and CHANGELOG entry
  (was scripts/copy-yaml-assets.mjs; actual helper is
  packages/dev-tools/src/copy-yaml-assets.ts).
- Pin deepMerge __proto__-as-override-key defense with an adversarial
  test (uses JSON.parse to construct __proto__ as a real own-property —
  object literal syntax is special-cased and does not exercise the path).
  Asserts no Object.prototype pollution and that the resulting object,
  having a null prototype, does not inherit polluted keys.

Per jdutton review on PR #120.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jun 5, 2026

Copy link
Copy Markdown

@ejdutton ejdutton merged commit 97e0903 into main Jun 5, 2026
7 checks passed
@ejdutton ejdutton deleted the feat/113-linkauth-engine branch June 5, 2026 18:30
jdutton added a commit that referenced this pull request Jun 5, 2026
Resolves the CHANGELOG.md [Unreleased] conflict against #120 (linkAuth,
now on main). Beyond the union, consolidated the two redundant
empirical-compat-harness Internal bullets (v1 scaffold + v2 foundations,
same subsystem/same release cycle) into one coherent entry preserving
all facts, and ordered Internal as features → compat-empirical cluster.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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.

2 participants