sealcode 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sealcode",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Lock your source code in your own git repo. Stop AI agents, scrapers, and curious eyes from reading what's yours.",
5
5
  "keywords": [
6
6
  "encryption",
package/src/cli-grants.js CHANGED
@@ -706,12 +706,64 @@ async function runLockdown({ projectRoot, confirm = true } = {}) {
706
706
  return;
707
707
  }
708
708
  }
709
- const res = await request('POST', `/api/v1/projects/${linked.projectId}/lockdown`, {
710
- auth: true,
711
- body: {},
712
- });
713
- ui.ok(`Lockdown complete revoked ${ui.c.bold(res.revokedCount)} grant(s).`);
714
- if (res.revokedCount > 0) {
709
+ // sealcode@1.3.3 the server's bulk /lockdown endpoint silently 307s to
710
+ // /login for reasons that don't reproduce on any other CLI route — even
711
+ // a no-op handler stub gets intercepted. Until that's root-caused on the
712
+ // server, fall back to client-side iteration: list grants, revoke each
713
+ // active one individually. Each /revoke call works correctly and fires
714
+ // the same publishGrantLock event the bulk endpoint would have. The
715
+ // per-grant calls are issued in parallel so the wall-clock cost of a
716
+ // 20-grant lockdown is still ~1 round-trip plus the slowest revoke.
717
+ let bulkRevoked = null;
718
+ try {
719
+ const bulk = await request('POST', `/api/v1/projects/${linked.projectId}/lockdown`, {
720
+ auth: true,
721
+ body: {},
722
+ });
723
+ if (typeof bulk.revokedCount === 'number') {
724
+ bulkRevoked = bulk.revokedCount;
725
+ }
726
+ } catch (_) {
727
+ // fall through to per-grant fallback below
728
+ }
729
+
730
+ if (bulkRevoked === null) {
731
+ // Fallback: enumerate active grants and revoke them one by one.
732
+ const list = await request(
733
+ 'GET',
734
+ `/api/v1/projects/${linked.projectId}/grants?status=active,paused&limit=200`,
735
+ { auth: true },
736
+ );
737
+ const grants = Array.isArray(list.grants) ? list.grants : [];
738
+ const live = grants.filter((g) => g.status === 'active' || g.status === 'paused');
739
+ if (live.length === 0) {
740
+ ui.ok('Lockdown complete — no active grants to revoke.');
741
+ return;
742
+ }
743
+ const results = await Promise.allSettled(
744
+ live.map((g) =>
745
+ request('DELETE', `/api/v1/grants/${encodeURIComponent(g.id)}`, { auth: true }),
746
+ ),
747
+ );
748
+ const ok = results.filter((r) => r.status === 'fulfilled').length;
749
+ const fail = results.length - ok;
750
+ if (fail > 0) {
751
+ ui.warn(`Revoked ${ok}/${live.length} grants — ${fail} failed.`);
752
+ for (const r of results) {
753
+ if (r.status === 'rejected') {
754
+ ui.fail(` - ${r.reason && r.reason.message ? r.reason.message : String(r.reason)}`);
755
+ }
756
+ }
757
+ }
758
+ ui.ok(`Lockdown complete — revoked ${ui.c.bold(ok)} grant(s).`);
759
+ if (ok > 0) {
760
+ ui.hint(' All connected watchers will re-lock within ~1 second.');
761
+ }
762
+ return;
763
+ }
764
+
765
+ ui.ok(`Lockdown complete — revoked ${ui.c.bold(bulkRevoked)} grant(s).`);
766
+ if (bulkRevoked > 0) {
715
767
  ui.hint(' All connected watchers will re-lock within ~1 second.');
716
768
  }
717
769
  }
package/src/seal.js CHANGED
@@ -49,6 +49,18 @@ function applyStubs(projectRoot, stubs) {
49
49
  } else {
50
50
  throw new Error(`stub for ${relPath} must be string or { contents, mode? }`);
51
51
  }
52
+ // sealcode@1.3.3 — if the target file already exists and was set to
53
+ // 0444 (by RO-mode unlock), writeFileSync will EACCES. This is exactly
54
+ // the failure that left RO-violation re-locks half-complete: the
55
+ // watcher detected the tamper, started the re-lock, hit EACCES on the
56
+ // first stub, then exited — leaving every plaintext file exposed on
57
+ // disk with NO watcher running. Force the file writable before we
58
+ // overwrite it.
59
+ try {
60
+ if (fs.existsSync(target)) {
61
+ fs.chmodSync(target, 0o644);
62
+ }
63
+ } catch (_) { /* best-effort; the writeFile below will surface any real error */ }
52
64
  writeFileEnsuringDir(target, contents, mode);
53
65
  written[relPath] = true;
54
66
  }