@cybedefend/vibedefend 1.1.1

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.
Files changed (103) hide show
  1. package/LICENSE +77 -0
  2. package/README.md +120 -0
  3. package/bin/vibedefend.js +19 -0
  4. package/dist/auth/auth-store.js +170 -0
  5. package/dist/auth/auth-store.js.map +1 -0
  6. package/dist/auth/auth.js +125 -0
  7. package/dist/auth/auth.js.map +1 -0
  8. package/dist/auth/callback-server.js +216 -0
  9. package/dist/auth/callback-server.js.map +1 -0
  10. package/dist/auth/pkce.js +31 -0
  11. package/dist/auth/pkce.js.map +1 -0
  12. package/dist/auth/token-exchange.js +83 -0
  13. package/dist/auth/token-exchange.js.map +1 -0
  14. package/dist/clients/claude-code.js +170 -0
  15. package/dist/clients/claude-code.js.map +1 -0
  16. package/dist/clients/codex.js +378 -0
  17. package/dist/clients/codex.js.map +1 -0
  18. package/dist/clients/cursor-guards-rules.js +94 -0
  19. package/dist/clients/cursor-guards-rules.js.map +1 -0
  20. package/dist/clients/cursor.js +172 -0
  21. package/dist/clients/cursor.js.map +1 -0
  22. package/dist/clients/detect.js +86 -0
  23. package/dist/clients/detect.js.map +1 -0
  24. package/dist/clients/registry.js +41 -0
  25. package/dist/clients/registry.js.map +1 -0
  26. package/dist/clients/types.js +2 -0
  27. package/dist/clients/types.js.map +1 -0
  28. package/dist/clients/vscode.js +187 -0
  29. package/dist/clients/vscode.js.map +1 -0
  30. package/dist/clients/windsurf.js +151 -0
  31. package/dist/clients/windsurf.js.map +1 -0
  32. package/dist/config.js +32 -0
  33. package/dist/config.js.map +1 -0
  34. package/dist/custom-regions.js +112 -0
  35. package/dist/custom-regions.js.map +1 -0
  36. package/dist/diagnostics.js +122 -0
  37. package/dist/diagnostics.js.map +1 -0
  38. package/dist/doctor.js +125 -0
  39. package/dist/doctor.js.map +1 -0
  40. package/dist/guards-evaluator/bucketing.js +83 -0
  41. package/dist/guards-evaluator/bucketing.js.map +1 -0
  42. package/dist/guards-evaluator/evaluate.js +272 -0
  43. package/dist/guards-evaluator/evaluate.js.map +1 -0
  44. package/dist/guards-evaluator/glob.js +148 -0
  45. package/dist/guards-evaluator/glob.js.map +1 -0
  46. package/dist/guards-evaluator/index.js +9 -0
  47. package/dist/guards-evaluator/index.js.map +1 -0
  48. package/dist/guards-evaluator/preprocess.js +174 -0
  49. package/dist/guards-evaluator/preprocess.js.map +1 -0
  50. package/dist/guards-evaluator/redact.js +111 -0
  51. package/dist/guards-evaluator/redact.js.map +1 -0
  52. package/dist/guards-evaluator/regex.js +125 -0
  53. package/dist/guards-evaluator/regex.js.map +1 -0
  54. package/dist/guards-evaluator/types.js +2 -0
  55. package/dist/guards-evaluator/types.js.map +1 -0
  56. package/dist/guards-evaluator/validation.js +115 -0
  57. package/dist/guards-evaluator/validation.js.map +1 -0
  58. package/dist/hook-runner.js +6680 -0
  59. package/dist/hooks/install.js +169 -0
  60. package/dist/hooks/install.js.map +1 -0
  61. package/dist/hooks/runtime/api.js +167 -0
  62. package/dist/hooks/runtime/api.js.map +1 -0
  63. package/dist/hooks/runtime/config.js +60 -0
  64. package/dist/hooks/runtime/config.js.map +1 -0
  65. package/dist/hooks/runtime/emit.js +45 -0
  66. package/dist/hooks/runtime/emit.js.map +1 -0
  67. package/dist/hooks/runtime/fetch-rules.js +154 -0
  68. package/dist/hooks/runtime/fetch-rules.js.map +1 -0
  69. package/dist/hooks/runtime/guard-rules-cache.js +217 -0
  70. package/dist/hooks/runtime/guard-rules-cache.js.map +1 -0
  71. package/dist/hooks/runtime/guard-violations-buffer.js +105 -0
  72. package/dist/hooks/runtime/guard-violations-buffer.js.map +1 -0
  73. package/dist/hooks/runtime/pre-compact.js +41 -0
  74. package/dist/hooks/runtime/pre-compact.js.map +1 -0
  75. package/dist/hooks/runtime/resolve.js +206 -0
  76. package/dist/hooks/runtime/resolve.js.map +1 -0
  77. package/dist/hooks/runtime/session-review.js +198 -0
  78. package/dist/hooks/runtime/session-review.js.map +1 -0
  79. package/dist/hooks/runtime/session-start.js +101 -0
  80. package/dist/hooks/runtime/session-start.js.map +1 -0
  81. package/dist/hooks/runtime/sniff.js +112 -0
  82. package/dist/hooks/runtime/sniff.js.map +1 -0
  83. package/dist/hooks/runtime/types.js +22 -0
  84. package/dist/hooks/runtime/types.js.map +1 -0
  85. package/dist/hooks/runtime/user-prompt-submit.js +154 -0
  86. package/dist/hooks/runtime/user-prompt-submit.js.map +1 -0
  87. package/dist/index.js +129 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/install.js +183 -0
  90. package/dist/install.js.map +1 -0
  91. package/dist/login.js +335 -0
  92. package/dist/login.js.map +1 -0
  93. package/dist/prompts.js +134 -0
  94. package/dist/prompts.js.map +1 -0
  95. package/dist/self-update.js +177 -0
  96. package/dist/self-update.js.map +1 -0
  97. package/dist/status.js +58 -0
  98. package/dist/status.js.map +1 -0
  99. package/dist/utils.js +84 -0
  100. package/dist/utils.js.map +1 -0
  101. package/dist/version.js +23 -0
  102. package/dist/version.js.map +1 -0
  103. package/package.json +73 -0
package/LICENSE ADDED
@@ -0,0 +1,77 @@
1
+ Business Source License 1.1
2
+
3
+ Licensor: CybeDefend
4
+ Licensed Work: @cybedefend/vibedefend
5
+ Additional Use Grant: Production use is permitted except to offer a service that competes with CybeDefend.
6
+ Change Date: 2030-05-25
7
+ Change License: Apache-2.0
8
+
9
+ License text copyright (c) 2024 MariaDB plc, All Rights Reserved.
10
+ "Business Source License" is a trademark of MariaDB plc.
11
+
12
+ Terms
13
+
14
+ The Licensor hereby grants you the right to copy, modify, create derivative
15
+ works, redistribute, and make non-production use of the Licensed Work. The
16
+ Licensor may make an Additional Use Grant, above, permitting limited production
17
+ use.
18
+
19
+ Effective on the Change Date, or the fourth anniversary of the first publicly
20
+ available distribution of a specific version of the Licensed Work under this
21
+ License, whichever comes first, the Licensor hereby grants you rights under the
22
+ terms of the Change License, and the rights granted in the paragraph above
23
+ terminate.
24
+
25
+ If your use of the Licensed Work does not comply with the requirements currently
26
+ in effect as described in this License, you must purchase a commercial license
27
+ from the Licensor, its affiliated entities, or authorized resellers, or you must
28
+ refrain from using the Licensed Work.
29
+
30
+ All copies of the original and modified Licensed Work, and derivative works of
31
+ the Licensed Work, are subject to this License. This License applies separately
32
+ for each version of the Licensed Work and the Change Date may vary for each
33
+ version of the Licensed Work released by Licensor.
34
+
35
+ You must conspicuously display this License on each original or modified copy
36
+ of the Licensed Work. If you receive the Licensed Work in original or modified
37
+ form from a third party, the terms and conditions set forth in this License
38
+ apply to your use of that work.
39
+
40
+ Any use of the Licensed Work in violation of this License will automatically
41
+ terminate your rights under this License for the current and all other versions
42
+ of the Licensed Work.
43
+
44
+ This License does not grant you any right in any trademark or logo of Licensor
45
+ or its affiliates (provided that you may use a trademark or logo of Licensor as
46
+ expressly required by this License).
47
+
48
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
49
+ "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS
50
+ OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY,
51
+ FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
52
+
53
+ MariaDB hereby grants you permission to use this License's text to license your
54
+ works, and to refer to it using the trademark "Business Source License", as
55
+ long as you comply with the Covenants of Licensor below.
56
+
57
+ Covenants of Licensor
58
+
59
+ In consideration of the right to use this License's text and the "Business
60
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
61
+ other recipients of the licensed work to be provided by Licensor:
62
+
63
+ To specify as the Change License the GPL Version 2.0 or any later version, or a
64
+ license that is compatible with GPL Version 2.0 or a later version, where
65
+ "compatible" means that software provided under the Change License can be
66
+ included in a program with software provided under GPL Version 2.0 or a later
67
+ version. Licensor may specify additional Change Licenses without limitation.
68
+
69
+ To either: (a) specify an additional grant of rights to use that does not impose
70
+ any additional restriction on the right granted in this License, as the
71
+ Additional Use Grant; or (b) insert the text "None".
72
+
73
+ Notice
74
+
75
+ The Business Source License (this document, or the "License") is not an Open
76
+ Source license. However, the Licensed Work will eventually be made available
77
+ under an Open Source License, as stated in this License.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # `@cybedefend/vibedefend`
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@cybedefend/vibedefend.svg)](https://www.npmjs.com/package/@cybedefend/vibedefend)
4
+ [![node](https://img.shields.io/badge/node-%3E%3D18.17-brightgreen.svg)](https://nodejs.org)
5
+ [![license](https://img.shields.io/badge/license-BUSL--1.1-yellow.svg)](./LICENSE)
6
+
7
+ > **VibeDefend** — one-shot installer that connects your AI coding agent
8
+ > to CybeDefend. Each time the agent edits code, the right business and
9
+ > security rules for the change land in its context. Each time you commit
10
+ > work, a quick gap analysis catches the rules you never wrote down.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npx -y @cybedefend/vibedefend@latest install
16
+ ```
17
+
18
+ Works on **macOS, Linux, and Windows** (PowerShell, cmd, bash, zsh, fish,
19
+ Git Bash — pick any). Requires **Node 18.17 or later** — most users already
20
+ have it because Claude Code / Cursor / Codex ship with a bundled Node.
21
+
22
+ The installer is fully interactive: pick a region, pick which agents to
23
+ wire (auto-detected), confirm. That's it.
24
+
25
+ Prefer a global install?
26
+
27
+ ```bash
28
+ npm install -g @cybedefend/vibedefend && vibedefend install
29
+ # or pnpm / yarn — same package
30
+ ```
31
+
32
+ ## Supported agents
33
+
34
+ VibeDefend auto-detects and wires whichever of these you have installed.
35
+
36
+ | Capability | Claude Code | Cursor | OpenAI Codex | Windsurf | VS Code Copilot |
37
+ |---|:---:|:---:|:---:|:---:|:---:|
38
+ | **MCP server install** | ✅ | ✅ | ✅ | ✅ | ✅ |
39
+ | **Business + Security Rules** *(injected pre-edit)* | ✅ | ✅ | ✅ | ⚠️ writes only | ✅ |
40
+ | **Action Guards** *(hard block on deny)* | ✅ all tools | ✅ all tools | ✅ all tools | ⚠️ writes + MCP fallback¹ | ❌ not yet wired |
41
+ | **Session Start** *(loads doctrine + proposals inbox)* | ✅ | ✅ | ✅ | ⚠️ proxied² | ✅ |
42
+ | **Session Review** *(end-of-session gap analysis)* | ✅ | ✅ | ✅ | ✅ | ✅ |
43
+ | **PreCompact** *(long-session gap analysis)* | ✅ | ✅ | ❌ no event | ❌ no event | ✅ |
44
+ | **Doctrine backstop** *(per-prompt reminder)* | ✅ | ❌ | ✱ via MCP³ | ❌ | ❌ |
45
+ | **Min version** | latest | ≥ 1.7 | latest | latest | ≥ 1.110 |
46
+
47
+ **Legend** — ✅ supported · ⚠️ supported with caveats · ❌ not exposed by the agent · ✱ alternate mechanism
48
+
49
+ ¹ Windsurf's `pre_write_code` hook hard-blocks on file writes only. For non-write tool calls (Read / Bash / WebFetch) the installer drops a snippet into `.windsurfrules` instructing the agent to call `cybe_guards_check` via MCP before sensitive actions — soft enforcement that relies on the model following its rules file.
50
+
51
+ ² Windsurf has no native SessionStart event. We wire `pre_user_prompt`, which fires on every turn. The hook is idempotent and cheap (one GET to the proposals endpoint, returns "0 pending" once the inbox is empty), so the per-turn cost is negligible.
52
+
53
+ ³ Codex follows the doctrine via the MCP server's `Server.instructions` field on each session, removing the need for a per-prompt reminder hook.
54
+
55
+ **Codex setup gotcha:** Codex 0.131+ requires you to approve each hook via the `/hooks` panel inside Codex *before* they fire. After running `vibedefend install`, open Codex, run `/hooks`, and trust the `cybedefend` entries — until you do that the panel will show `Installed N / Active 0`.
56
+
57
+ Unchecked agents stay untouched. Re-run `vibedefend install` later to
58
+ toggle any on or off.
59
+
60
+ ## Commands
61
+
62
+ ```
63
+ vibedefend install Set up MCP + hooks (interactive)
64
+ vibedefend update Refresh hooks to the latest version
65
+ vibedefend update --self Upgrade the CLI itself
66
+ vibedefend status Read-only install report
67
+ vibedefend doctor Diagnose and repair common issues
68
+ vibedefend login (Re-)authenticate against the CybeDefend API
69
+ vibedefend uninstall Remove every VibeDefend-installed file
70
+ vibedefend --help Full help
71
+ ```
72
+
73
+ After install you have one tiny file to drop in each repo you want
74
+ monitored — a `.cybedefend/config.json` with your project UUID:
75
+
76
+ ```json
77
+ { "projectId": "<your-cybedefend-project-uuid>" }
78
+ ```
79
+
80
+ You can grab the UUID from the project page on the EU dashboard
81
+ ([eu.cybedefend.com](https://eu.cybedefend.com)) or the US dashboard
82
+ ([us.cybedefend.com](https://us.cybedefend.com)).
83
+
84
+ ## Updating
85
+
86
+ Every time you run `vibedefend …`, a non-blocking check runs against npm
87
+ in the background. When a newer version is available you get a one-line
88
+ notice; run `vibedefend update --self` whenever you feel like it. Already-
89
+ installed hooks pull rule changes live, so most updates are silent.
90
+
91
+ ## Uninstalling
92
+
93
+ ```bash
94
+ vibedefend uninstall
95
+ # Then drop the global package if you installed it:
96
+ npm uninstall -g @cybedefend/vibedefend
97
+ ```
98
+
99
+ ## Sibling: `cybedefend-cli`
100
+
101
+ [`cybedefend-cli`](https://github.com/CybeDefend/cybedefend-cli) is the
102
+ platform CLI (scan, list vulnerabilities, manage projects from your
103
+ terminal or CI). VibeDefend handles the AI-agent integration side; the
104
+ two are complementary and unaware of each other.
105
+
106
+ ## Documentation
107
+
108
+ Full documentation, configuration reference, and troubleshooting at
109
+ **[docs.cybedefend.com](https://docs.cybedefend.com)**.
110
+
111
+ ## Support
112
+
113
+ - Bug reports / feature requests:
114
+ [support@cybedefend.com](mailto:support@cybedefend.com)
115
+ - Email: [support@cybedefend.com](mailto:support@cybedefend.com)
116
+ - Status: [status.cybedefend.com](https://status.cybedefend.com)
117
+
118
+ ## License
119
+
120
+ BUSL-1.1 — see [LICENSE](./LICENSE). Copyright 2026 CybeDefend. Converts to Apache-2.0 on 2030-05-25.
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ // Thin shim — calls the compiled dist or runs from src in dev. Published
3
+ // builds ship `dist/index.js`; local `pnpm dev` uses tsx via the package's
4
+ // `dev` script.
5
+ import { fileURLToPath } from 'node:url';
6
+ import { dirname, join } from 'node:path';
7
+ import { existsSync } from 'node:fs';
8
+
9
+ const here = dirname(fileURLToPath(import.meta.url));
10
+ const distEntry = join(here, '..', 'dist', 'index.js');
11
+
12
+ if (existsSync(distEntry)) {
13
+ await import(distEntry);
14
+ } else {
15
+ console.error(
16
+ 'vibedefend: dist/ not found. Run `pnpm run build` first, or use `pnpm dev` for source-mode.',
17
+ );
18
+ process.exit(1);
19
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Persistent storage for the OAuth token bundle.
3
+ *
4
+ * Storage strategy (most secure first):
5
+ * macOS → macOS Keychain via `security` CLI (no install required).
6
+ * Linux → ~/.cybedefend/auth.json with mode 0600.
7
+ * Windows → ~/.cybedefend/auth.json with mode 0600 (best-effort; NTFS ACLs
8
+ * are not set by Node's writeFileSync, but it is an improvement
9
+ * over environment variables for interactive use).
10
+ *
11
+ * The entire StoredAuth object is JSON-serialised and stored as the keychain
12
+ * password blob, so all PKCE / OIDC state travels with it. On macOS the OS
13
+ * provides the encryption; on Linux the file permissions are the only control.
14
+ *
15
+ * Service name: "Codex MCP Credentials" — intentionally the same service that
16
+ * `resolveTokenCodexMacOS` reads in resolve.ts, so a Codex-style lookup
17
+ * (account = "<mcpName>|vibedefend") also picks up the new bundle and the
18
+ * existing `resolveToken` path still works without modification during
19
+ * the migration period.
20
+ */
21
+ import { execFileSync } from 'node:child_process';
22
+ import { homedir } from 'node:os';
23
+ import { join } from 'node:path';
24
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, } from 'node:fs';
25
+ // ─── Constants ───────────────────────────────────────────────────────────────
26
+ const KEYCHAIN_SERVICE = 'Codex MCP Credentials';
27
+ /** Account shape: "<mcpName>|vibedefend" — matches resolveTokenCodexMacOS. */
28
+ function accountFor(mcpName) {
29
+ return `${mcpName}|vibedefend`;
30
+ }
31
+ // ─── Public API ───────────────────────────────────────────────────────────────
32
+ /** Persist an auth bundle for the given MCP name. */
33
+ export function storeAuth(mcpName, auth) {
34
+ const blob = JSON.stringify(auth);
35
+ if (process.platform === 'darwin') {
36
+ storeMacOSKeychain(mcpName, blob);
37
+ }
38
+ else {
39
+ storeFileFallback(blob);
40
+ }
41
+ }
42
+ /**
43
+ * Load the stored auth bundle for the given MCP name.
44
+ * Returns null if nothing is stored or the blob is invalid.
45
+ */
46
+ export function loadAuth(mcpName) {
47
+ let blob = null;
48
+ if (process.platform === 'darwin') {
49
+ blob = readMacOSKeychain(mcpName);
50
+ }
51
+ // File fallback — also checked on Linux/Windows and as fallback on macOS
52
+ // when the keychain entry is absent.
53
+ if (!blob)
54
+ blob = readFileFallback();
55
+ if (!blob)
56
+ return null;
57
+ try {
58
+ const parsed = JSON.parse(blob);
59
+ // refreshToken is OPTIONAL: Logto only emits one when the client app has
60
+ // the Refresh Token grant explicitly enabled AND the user requested
61
+ // offline_access. If absent, we can still operate — the user just has to
62
+ // `vibedefend login` again when access_token expires (no transparent refresh).
63
+ if (typeof parsed.accessToken === 'string' &&
64
+ typeof parsed.expiresAt === 'number' &&
65
+ typeof parsed.logtoEndpoint === 'string' &&
66
+ typeof parsed.cliAppId === 'string' &&
67
+ typeof parsed.logtoResource === 'string') {
68
+ return parsed;
69
+ }
70
+ return null;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ /** Remove stored credentials for the given MCP name. */
77
+ export function clearAuth(mcpName) {
78
+ if (process.platform === 'darwin') {
79
+ try {
80
+ execFileSync('security', [
81
+ 'delete-generic-password',
82
+ '-s', KEYCHAIN_SERVICE,
83
+ '-a', accountFor(mcpName),
84
+ ], { stdio: 'ignore' });
85
+ }
86
+ catch {
87
+ // Not found — that is fine.
88
+ }
89
+ }
90
+ // Also clear the file fallback (it may hold an older copy on non-macOS).
91
+ const filePath = fileFallbackPath();
92
+ if (existsSync(filePath)) {
93
+ try {
94
+ writeFileSync(filePath, '', { mode: 0o600 });
95
+ }
96
+ catch {
97
+ // Best-effort.
98
+ }
99
+ }
100
+ }
101
+ // ─── macOS Keychain helpers ───────────────────────────────────────────────────
102
+ function storeMacOSKeychain(mcpName, blob) {
103
+ // Delete any existing entry first — `add-generic-password` fails if the
104
+ // account already exists.
105
+ try {
106
+ execFileSync('security', [
107
+ 'delete-generic-password',
108
+ '-s', KEYCHAIN_SERVICE,
109
+ '-a', accountFor(mcpName),
110
+ ], { stdio: 'ignore' });
111
+ }
112
+ catch {
113
+ // Not found — that is fine.
114
+ }
115
+ execFileSync('security', [
116
+ 'add-generic-password',
117
+ '-s', KEYCHAIN_SERVICE,
118
+ '-a', accountFor(mcpName),
119
+ '-w', blob,
120
+ ], { stdio: 'ignore' });
121
+ }
122
+ function readMacOSKeychain(mcpName) {
123
+ try {
124
+ const out = execFileSync('security', [
125
+ 'find-generic-password',
126
+ '-s', KEYCHAIN_SERVICE,
127
+ '-a', accountFor(mcpName),
128
+ '-w',
129
+ ], {
130
+ encoding: 'utf8',
131
+ stdio: ['ignore', 'pipe', 'ignore'],
132
+ timeout: 3000,
133
+ });
134
+ return out.trim() || null;
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }
140
+ // ─── File fallback (Linux / Windows) ─────────────────────────────────────────
141
+ function fileFallbackPath() {
142
+ return join(homedir(), '.cybedefend', 'auth.json');
143
+ }
144
+ function storeFileFallback(blob) {
145
+ const dir = join(homedir(), '.cybedefend');
146
+ mkdirSync(dir, { recursive: true });
147
+ const p = fileFallbackPath();
148
+ writeFileSync(p, blob, { mode: 0o600 });
149
+ try {
150
+ // Belt-and-suspenders: explicitly chmod after write (some umask configs
151
+ // ignore the mode option on writeFileSync).
152
+ chmodSync(p, 0o600);
153
+ }
154
+ catch {
155
+ // Non-fatal on Windows (no POSIX perms).
156
+ }
157
+ }
158
+ function readFileFallback() {
159
+ const p = fileFallbackPath();
160
+ if (!existsSync(p))
161
+ return null;
162
+ try {
163
+ const content = readFileSync(p, 'utf8');
164
+ return content || null;
165
+ }
166
+ catch {
167
+ return null;
168
+ }
169
+ }
170
+ //# sourceMappingURL=auth-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-store.js","sourceRoot":"","sources":["../../src/auth/auth-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Single entry point for auth state used by all hook callers.
3
+ *
4
+ * Responsibilities:
5
+ * - Return a valid access_token, refreshing transparently when near expiry.
6
+ * - Deduplicate concurrent refresh calls (many hooks may fire simultaneously
7
+ * at session start; we must not hammer Logto with N refresh requests).
8
+ * - On refresh_token rejection, purge local credentials and throw
9
+ * LoginRequiredError so the caller can trigger the fail-open UNVERIFIED
10
+ * banner.
11
+ *
12
+ * This module is intentionally side-effect-free at import time — the
13
+ * in-flight deduplication state is module-level, but nothing fires until
14
+ * getValidAccessToken() is called.
15
+ */
16
+ import { loadAuth, storeAuth, clearAuth, } from './auth-store.js';
17
+ import { refreshAccessToken, RefreshTokenInvalidError } from './token-exchange.js';
18
+ // ─── Errors ───────────────────────────────────────────────────────────────────
19
+ /**
20
+ * Thrown when a valid access_token cannot be obtained because:
21
+ * - No credentials are stored at all, OR
22
+ * - The refresh_token has been rejected / revoked by Logto.
23
+ *
24
+ * Hook callers that catch this should fall back to the loud UNVERIFIED
25
+ * banner (fail-open semantics) so the dev session is never hard-blocked.
26
+ */
27
+ export class LoginRequiredError extends Error {
28
+ constructor(msg = 'VibeDefend login required. Run `vibedefend login`.') {
29
+ super(msg);
30
+ this.name = 'LoginRequiredError';
31
+ }
32
+ }
33
+ // ─── In-flight deduplication ──────────────────────────────────────────────────
34
+ /**
35
+ * When multiple hooks fire in the same millisecond (session-start, guard-check,
36
+ * fetch-rules all launching concurrently) and the token is stale, they would all
37
+ * attempt a refresh simultaneously. We de-duplicate by keeping a single promise
38
+ * while a refresh is in flight and having every concurrent waiter share it.
39
+ */
40
+ let inFlightRefresh = null;
41
+ // ─── Public API ───────────────────────────────────────────────────────────────
42
+ /**
43
+ * Return a valid access_token for the given MCP name.
44
+ *
45
+ * - If the stored token is fresh (now < expiresAt), return it immediately.
46
+ * - If stale, initiate (or join an in-flight) refresh.
47
+ * - If the refresh_token is rejected, purge credentials and throw
48
+ * LoginRequiredError.
49
+ * - If no credentials are stored, throw LoginRequiredError immediately.
50
+ *
51
+ * Concurrent calls share a single in-flight refresh promise — Logto sees
52
+ * exactly one refresh request per expiry cycle.
53
+ */
54
+ export async function getValidAccessToken(mcpName) {
55
+ const stored = loadAuth(mcpName);
56
+ if (!stored) {
57
+ throw new LoginRequiredError('No stored credentials. Run `vibedefend login`.');
58
+ }
59
+ const now = Date.now();
60
+ if (now < stored.expiresAt) {
61
+ // Token is still valid.
62
+ return stored.accessToken;
63
+ }
64
+ // Token is stale. If we don't have a refresh_token (Logto didn't issue one
65
+ // because offline_access wasn't granted on this client), we cannot refresh
66
+ // silently — the user must `vibedefend login` again.
67
+ if (!stored.refreshToken) {
68
+ throw new LoginRequiredError('Access token expired and no refresh_token is available. Run `vibedefend login` again.');
69
+ }
70
+ // Token is stale — refresh (deduped).
71
+ if (!inFlightRefresh) {
72
+ inFlightRefresh = doRefresh(mcpName, stored).finally(() => {
73
+ inFlightRefresh = null;
74
+ });
75
+ }
76
+ return await inFlightRefresh;
77
+ }
78
+ /**
79
+ * Synchronous check: does the given MCP name have any stored credentials?
80
+ * Used by the install chain to skip the login prompt on re-install when
81
+ * credentials are already present.
82
+ */
83
+ export function hasStoredAuth(mcpName) {
84
+ return loadAuth(mcpName) !== null;
85
+ }
86
+ // ─── Internal refresh logic ───────────────────────────────────────────────────
87
+ async function doRefresh(mcpName, stored) {
88
+ // Safety net: getValidAccessToken already checks this, but type-narrow here
89
+ // too so the call below doesn't pass undefined as a string.
90
+ if (!stored.refreshToken) {
91
+ throw new LoginRequiredError('No refresh_token available; re-login required.');
92
+ }
93
+ try {
94
+ const fresh = await refreshAccessToken({
95
+ logtoEndpoint: stored.logtoEndpoint,
96
+ clientId: stored.cliAppId,
97
+ refreshToken: stored.refreshToken,
98
+ resource: stored.logtoResource,
99
+ });
100
+ const updated = {
101
+ ...stored,
102
+ accessToken: fresh.access_token,
103
+ // Logto issues a new refresh_token on every refresh (rolling tokens).
104
+ // Fall back to the existing one if the response omits it.
105
+ refreshToken: fresh.refresh_token ?? stored.refreshToken,
106
+ idToken: fresh.id_token ?? stored.idToken,
107
+ expiresAt: Date.now() + (fresh.expires_in - 30) * 1000,
108
+ };
109
+ storeAuth(mcpName, updated);
110
+ return updated.accessToken;
111
+ }
112
+ catch (e) {
113
+ if (e instanceof RefreshTokenInvalidError) {
114
+ // Refresh token revoked / expired — nuke local state so the next
115
+ // `vibedefend login` starts from a clean slate.
116
+ clearAuth(mcpName);
117
+ throw new LoginRequiredError('Your session has expired. Run `vibedefend login` to re-authenticate.');
118
+ }
119
+ // Transient (network, DNS, Logto down): re-throw so the caller can
120
+ // trigger the fail-open banner. Do NOT clear credentials — the user
121
+ // should not be forced to log in again for a transient failure.
122
+ throw e;
123
+ }
124
+ }
125
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth/auth.ts"],"names":[],"mappings":"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"}