fix: skip auth check in extension push --dry-run#718
Conversation
`--dry-run` only builds the archive locally and never contacts the registry, so requiring authentication is unnecessary. This wraps the auth loading (step 3) and collective membership validation (step 4) inside a `!dryRun` guard so they are only enforced on real pushes. The actual push paths (steps 13 and 17) already have their own `!dryRun` guards, so credentials are never used in dry-run mode. Fixes #717 Co-authored-by: Blake Irvin <blakeirvin@me.com> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Review: APPROVED
This PR correctly implements skipping authentication for --dry-run mode in extension push. The logic is sound and type-safe.
Analysis
Code Quality: The changes maintain TypeScript strict mode compliance. The credentials variable is properly typed as | undefined and the non-null assertions (credentials!) are only used within code paths that are guaranteed to be non-dry-run (either inside explicit if (!options.dryRun) blocks or after the dry-run early return).
Security: The trade-off is correctly assessed in the PR description. Dry-run only validates the build locally and never contacts the registry, so authentication is genuinely unnecessary. The real push path still enforces full authentication and collective membership validation.
DDD: This is an appropriate application-layer change—dry-run is an orchestration concern, not domain logic.
Suggestions (non-blocking)
-
Test coverage for the new behavior: The existing
--dry-runintegration test still createsauth.json. Consider adding a dedicated test that explicitly verifies--dry-runsucceeds without any auth credentials, which would document and protect the new behavior. Example:Deno.test("extension push --dry-run succeeds without auth credentials", async () => { const tmpDir = await initTempRepo(); const fakeHome = await Deno.makeTempDir(); // No auth.json try { // ... create model and manifest ... const { code } = await runCli( ["extension", "push", manifestPath, "--repo-dir", tmpDir, "--dry-run", "-y"], { HOME: fakeHome, XDG_CONFIG_HOME: join(fakeHome, ".config") }, ); assertEquals(code, 0); } finally { /* cleanup */ } });
-
Minor style consideration: The
credentials!assertions are type-safe given the control flow, but if you prefer to avoid non-null assertions, you could extract the non-dry-run logic into a helper or use an early return pattern. This is purely stylistic—the current approach is clear and correct.
LGTM - the change is well-reasoned and correctly implemented.
There was a problem hiding this comment.
Adversarial Review
I traced every code path in the PR and attempted to find logic errors, security issues, and failure modes. Here are my findings:
Critical / High
None.
Medium
- Missing test coverage for new behavior (integration/extension_push_test.ts)
- The PR claims
--dry-runnow works without auth, but all existing dry-run tests (lines 218-394, 452-535, 649-751) createauth.jsonbefore running. There's no test that verifies dry-run succeeds when auth is absent. - Impact: If a future change breaks this behavior, CI won't catch it.
- Suggested fix: Add a test case that runs
--dry-runwithHOMEpointing to a directory withoutauth.jsonand asserts success.
- The PR claims
Low
- Non-null assertions mask future refactoring risks (extension_push.ts:343, 346, 499, 516, 530)
- The
credentials!assertions are currently safe because:- Lines 343, 346: Inside
if (!options.dryRun)where credentials was populated earlier - Lines 499, 516, 530: After the
if (options.dryRun) returnearly exit at line 484
- Lines 343, 346: Inside
- However, TypeScript's flow analysis can't prove this across the async function boundary. If someone moves code around without understanding the invariant, they could introduce a runtime null dereference.
- Impact: Low - TypeScript will catch most issues, and the control flow is straightforward.
- Mitigation: The code structure is clear and the comment at step 3 documents the intent.
- The
Security Assessment
The trade-off documented in the PR is acceptable:
- Dry-run only builds locally and never contacts the registry
- Server-side collective membership validation (step 4) is skipped, but local content collective validation (step 9c) still runs
- The real push path still enforces all authentication and authorization checks
Verdict
PASS - The control flow is sound, the non-null assertions are safe given the current structure, and the security trade-off is acceptable for the intended use case (CI validation without credentials). The missing test coverage is the main gap but doesn't block the PR.
Summary
swamp extension push --dry-runnow works without authentication!dryRunguard so they only run for real pushesWhat changed
In
src/cli/commands/extension_push.ts, the auth check and collective membership validation previously ran unconditionally before the dry-run exit point. Since--dry-runonly builds the archive locally and never contacts the registry, authentication is unnecessary.The
credentialsvariable is now typed as| undefinedand only populated when not in dry-run mode. The existing non-dry-run code paths (steps 13 and 17) already have their ownif (!options.dryRun)guards, so credentials are never accessed in dry-run mode.Trade-off
Dry-run will skip collective ownership validation. This is acceptable because:
Test plan
deno checkpassesdeno lintpassesdeno fmtpassesdeno run testpassesdeno run compilepassesswamp extension push <manifest> --dry-runsucceeds without authswamp extension push <manifest>without auth still fails with "Not authenticated"Fixes #717
🤖 Generated with Claude Code