recover the source from the published sourcemaps — 8 of 8 versions rebuild byte-identically - #18
recover the source from the published sourcemaps — 8 of 8 versions rebuild byte-identically#18yakimoto wants to merge 1 commit into
Conversation
…build byte-identically This repo advertised itself as the source of @wave-av/cli while containing none of it. Eight versions were published between 2026-04-03 and 2026-08-04 from a working copy never committed, so anyone who followed the repository link the package itself carries arrived at a README and a LICENSE while the code running on their machine existed in no public repository. The source was not lost. Every published version ships dist/index.js.map, and that sourcemap carries sourcesContent — not the names of the original files but their complete contents. All 70 TypeScript files here were extracted from it verbatim; nothing was inferred from the bundle or hand-written to match. The claim is checkable, so it was checked on every version rather than a sample: 1.0.0, 1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.0.6, 1.0.7 and 1.0.8 each rebuild to a dist/index.js that is BYTE-IDENTICAL to the artifact npm shipped. RECOVERY.md carries the three commands to re-derive any row. Two files were AUTHORED for this recovery and are labelled as such rather than left to look like they came out of the artifact: tsconfig.json and tsup.config.ts. A sourcemap contains source, not build configuration. They are not loose guesses — they are the settings under which the output matches the published bytes exactly, eight times over — but written is not recovered, and conflating the two would be the error this exercise exists to correct. package.json is the published manifest, which npm preserves in full. gitleaks: no leaks found, 251 KB scanned with this repo own .gitleaks.toml. That mattered: the sourcemap has been publicly downloadable since April, so a credential in it would have been a live four-month exposure rather than a risk created by recovering it. This unblocks the publish workflow in #17, whose verify job cannot run npm ci against a repo with no package.json. It does NOT create the tags the eight published versions never had; tagging each against its own cmp receipt is tracked separately. Refs wave-av/claude-workstation#1624
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_a509e33a-04e3-4635-a08f-28a23f8cc7ed) |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 35 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (99)
Comment |
ApprovabilityVerdict: Needs human review Unable to check for correctness in 8ce183a. Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
| console.log(`Recording ready: ${event.data.recording_url}`); | ||
| break; | ||
| default: | ||
| console.log(`Unhandled event type: ${event.type}`); |
There was a problem hiding this comment.
Semgrep identified a blocking 🔴 issue in your code:
Detected a logger that logs user input without properly neutralizing the output. The log message could contain characters like and and cause an attacker to forge log entries or include malicious content into the logs. Use proper input validation and/or output encoding to prevent log entries from being forged.
Dataflow graph
flowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>templates/webhook-handler/src/index.ts</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] req</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] event</a>"]
v3["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L43 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 43] `</a>"]
end
v2 --> v3
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L43 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 43] console.log(`Unhandled event type: ${event.type}`)</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by console-log-express.
You can view more details about this finding in the Semgrep AppSec Platform.
| console.log(`Stream ${event.data.stream_id} ended`); | ||
| break; | ||
| case "recording.ready": | ||
| console.log(`Recording ready: ${event.data.recording_url}`); |
There was a problem hiding this comment.
Semgrep identified a blocking 🔴 issue in your code:
Detected a logger that logs user input without properly neutralizing the output. The log message could contain characters like and and cause an attacker to forge log entries or include malicious content into the logs. Use proper input validation and/or output encoding to prevent log entries from being forged.
Dataflow graph
flowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>templates/webhook-handler/src/index.ts</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] req</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] event</a>"]
v3["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L40 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 40] `</a>"]
end
v2 --> v3
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L40 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 40] console.log(`Recording ready: ${event.data.recording_url}`)</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by console-log-express.
You can view more details about this finding in the Semgrep AppSec Platform.
| console.log(`Stream ${event.data.stream_id} started`); | ||
| break; | ||
| case "stream.ended": | ||
| console.log(`Stream ${event.data.stream_id} ended`); |
There was a problem hiding this comment.
Semgrep identified a blocking 🔴 issue in your code:
Detected a logger that logs user input without properly neutralizing the output. The log message could contain characters like and and cause an attacker to forge log entries or include malicious content into the logs. Use proper input validation and/or output encoding to prevent log entries from being forged.
Dataflow graph
flowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>templates/webhook-handler/src/index.ts</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] req</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] event</a>"]
v3["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L37 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 37] `</a>"]
end
v2 --> v3
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L37 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 37] console.log(`Stream ${event.data.stream_id} ended`)</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by console-log-express.
You can view more details about this finding in the Semgrep AppSec Platform.
|
|
||
| switch (event.type) { | ||
| case "stream.started": | ||
| console.log(`Stream ${event.data.stream_id} started`); |
There was a problem hiding this comment.
Semgrep identified a blocking 🔴 issue in your code:
Detected a logger that logs user input without properly neutralizing the output. The log message could contain characters like and and cause an attacker to forge log entries or include malicious content into the logs. Use proper input validation and/or output encoding to prevent log entries from being forged.
Dataflow graph
flowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>templates/webhook-handler/src/index.ts</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] req</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] event</a>"]
v3["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L34 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 34] `</a>"]
end
v2 --> v3
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L34 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 34] console.log(`Stream ${event.data.stream_id} started`)</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by console-log-express.
You can view more details about this finding in the Semgrep AppSec Platform.
| } | ||
|
|
||
| const event = req.body; | ||
| console.log(`Received event: ${event.type}`); |
There was a problem hiding this comment.
Semgrep identified a blocking 🔴 issue in your code:
Detected a logger that logs user input without properly neutralizing the output. The log message could contain characters like and and cause an attacker to forge log entries or include malicious content into the logs. Use proper input validation and/or output encoding to prevent log entries from being forged.
Dataflow graph
flowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>templates/webhook-handler/src/index.ts</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] req</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L29 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 29] event</a>"]
v3["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L30 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 30] `</a>"]
end
v2 --> v3
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/wave-av/cli/blob/8ce183a2aeac624cb9bbb9122b90ddfbfcef458f/templates/webhook-handler/src/index.ts#L30 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 30] console.log(`Received event: ${event.type}`)</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by console-log-express.
You can view more details about this finding in the Semgrep AppSec Platform.
| if (typeof current[key] !== "object" || current[key] === null) { | ||
| current[key] = {}; | ||
| } | ||
| current = current[key] as Record<string, unknown>; |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
Possibility of prototype polluting function detected. By adding or modifying attributes of an object prototype, it is possible to create attributes that exist on every object, or replace critical attributes with malicious ones. This can be problematic if the software depends on existence or non-existence of certain attributes, or uses pre-defined attributes of object prototype (such as hasOwnProperty, toString or valueOf). Possible mitigations might be: freezing the object prototype, using an object without prototypes (via Object.create(null) ), blocking modifications of attributes that resolve to object prototype, using Map instead of object.
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by prototype-pollution-loop.
You can view more details about this finding in the Semgrep AppSec Platform.
| if (current === null || current === undefined || typeof current !== "object") { | ||
| return undefined; | ||
| } | ||
| current = (current as Record<string, unknown>)[key]; |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
Possibility of prototype polluting function detected. By adding or modifying attributes of an object prototype, it is possible to create attributes that exist on every object, or replace critical attributes with malicious ones. This can be problematic if the software depends on existence or non-existence of certain attributes, or uses pre-defined attributes of object prototype (such as hasOwnProperty, toString or valueOf). Possible mitigations might be: freezing the object prototype, using an object without prototypes (via Object.create(null) ), blocking modifications of attributes that resolve to object prototype, using Map instead of object.
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by prototype-pollution-loop.
You can view more details about this finding in the Semgrep AppSec Platform.
PR Summary by QodoRecover @wave-av/cli source from published sourcemaps (1.0.0–1.0.8 reproducible)
AI Description
Diagram
High-Level Assessment
Files changed (99)
|
|
This PR has 6,617 reviewable changed lines after ignored/generated files are excluded, above this repository's 5,000-changed-line automatic review limit. Most of the diff comes from:
Comment |
| } else { | ||
| options.onError?.(new Error(`Max reconnect attempts (${maxAttempts}) exceeded`)); | ||
| options.onClose?.(); | ||
| } |
There was a problem hiding this comment.
🔴 Event and log streaming commands hang forever after the connection ends
The streaming helper finishes without signalling shutdown (options.onClose?.() at src/lib/sse-client.ts:162) when the connection is given up on, so wave listen and wave logs tail print "Disconnected" and then sit there forever until the user kills them.
Impact: Users and scripts running these commands are left with a hung process that never exits on its own.
Abort controller is never triggered on non-user-initiated termination
src/commands/listen/index.ts:61-63 and src/commands/logs/index.ts:85-87 keep the command alive with await new Promise(resolve => controller.signal.addEventListener("abort", () => resolve())). The controller is only aborted by SIGINT/SIGTERM (src/lib/sse-client.ts:50-54). Two paths terminate the stream without aborting:
- Max reconnect attempts exceeded —
options.onClose?.()is invoked atsrc/lib/sse-client.ts:162butcontroller.abort()is never called. - The server closes the stream cleanly — the read loop exits on
done(src/lib/sse-client.ts:96),connect()returns normally, and no reconnect or close notification happens at all.
In both cases the awaited promise never settles and the SIGINT/SIGTERM listeners registered at src/lib/sse-client.ts:52-53 are also never removed.
| } else { | |
| options.onError?.(new Error(`Max reconnect attempts (${maxAttempts}) exceeded`)); | |
| options.onClose?.(); | |
| } | |
| } else { | |
| options.onError?.(new Error(`Max reconnect attempts (${maxAttempts}) exceeded`)); | |
| options.onClose?.(); | |
| controller.abort(); | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
| } catch { | ||
| const defaults = getDefaultConfig(); | ||
| await saveConfig(defaults); | ||
| return defaults; | ||
| } |
There was a problem hiding this comment.
🔴 Saved settings are silently wiped whenever the settings file cannot be read or fails validation
The stored settings file is replaced with a fresh default one (await saveConfig(defaults) at src/lib/config/manager.ts:30-31) as soon as reading or validating it fails, so a user's organization and project settings are permanently destroyed instead of being reported as a problem.
Impact: A single malformed or partially outdated settings file silently deletes all of the user's saved project configuration.
Catch-all recovery path overwrites the on-disk file
loadConfig() wraps both the JSON parse and the zod validation in one try. The schema at src/lib/config/schema.ts:3-8 requires organizationId and organizationName for every project entry, so any project written without those fields (e.g. by wave config set, which can inject arbitrary nested keys via setNestedValue in src/commands/config/index.ts:78-94) makes waveConfigSchema.parse throw. The catch block then writes defaults back over the file, discarding all existing projects. A non-destructive alternative is to return defaults in memory (and/or back up the bad file) without overwriting it, and surface an error to the user.
Prompt for agents
In src/lib/config/manager.ts, loadConfig() catches every failure (JSON parse error or zod validation error) and immediately calls saveConfig(defaults), overwriting the user's config.json on disk. This destroys all stored project/organization data whenever the file is malformed or fails the strict schema in src/lib/config/schema.ts. Consider returning the defaults in memory without persisting them, or renaming the offending file to a .bak before writing defaults, and surfacing a warning so the user knows their configuration was not loaded.
Was this helpful? React with 👍 or 👎 to provide feedback.
| // Generate .gitignore (append if template already has one) | ||
| const gitignorePath = join(dir, ".gitignore"); | ||
| const gitignoreContent = `.env.local | ||
| .wave/ | ||
| node_modules/ | ||
| dist/ | ||
| `; | ||
| await writeFile(gitignorePath, gitignoreContent, "utf-8"); |
There was a problem hiding this comment.
🟡 Project scaffolding erases the starter project's ignore file
The newly created project's ignore file is unconditionally replaced (writeFile(gitignorePath, ...) at src/commands/init/index.ts:191) even when the starter files already provided one, so any ignore rules that shipped with the starter are lost.
Impact: Generated projects can accidentally commit files the template intended to keep out of version control.
Comment states append, implementation overwrites
The comment on src/commands/init/index.ts:184 explicitly says "append if template already has one", but copyDir (src/commands/init/index.ts:67-79) has already copied the template's .gitignore into dir, and the subsequent writeFile truncates it with the fixed four-line content. The link command implements the intended behaviour correctly via ensureGitignoreEntry (src/commands/link/index.ts:202-218), which reads and appends.
Prompt for agents
In src/commands/init/index.ts the generated .gitignore is written with writeFile, which overwrites any .gitignore that was copied from the template directory by copyDir, contradicting the adjacent comment ('append if template already has one'). Reuse an append-if-missing approach similar to ensureGitignoreEntry in src/commands/link/index.ts so template-provided ignore rules are preserved and the CLI-specific entries are only added when absent.
Was this helpful? React with 👍 or 👎 to provide feedback.
| .command("trigger <event>") | ||
| .description("Trigger a WAVE event manually") | ||
| .option("--override <pairs...>", "Override event data (key=value pairs)") | ||
| .option("--list", "List available trigger events") |
There was a problem hiding this comment.
🟡 Listing the available manual events is impossible without inventing an event name
The listing switch is attached to a command that demands an event name up front (.command("trigger <event>") at src/commands/trigger/index.ts:10), so running the documented listing form fails with a missing-argument error before it can do anything.
Impact: Users cannot discover which events they are allowed to trigger.
Required positional argument blocks the --list flag
Commander treats <event> as required, so wave trigger --list exits with "error: missing required argument 'event'". The --list branch at src/commands/trigger/index.ts:25-36 is only reachable with a throwaway value such as wave trigger x --list. Making the argument optional ([event]) and validating that it is present when --list is not passed would make the flag usable.
| .command("trigger <event>") | |
| .description("Trigger a WAVE event manually") | |
| .option("--override <pairs...>", "Override event data (key=value pairs)") | |
| .option("--list", "List available trigger events") | |
| .command("trigger [event]") | |
| .description("Trigger a WAVE event manually") | |
| .option("--override <pairs...>", "Override event data (key=value pairs)") | |
| .option("--list", "List available trigger events") |
Was this helpful? React with 👍 or 👎 to provide feedback.
| "dependencies": { | ||
| "@wave/sdk": "^2.0.0" | ||
| }, |
There was a problem hiding this comment.
🟡 Scaffolded starter projects fail to install their dependencies
Every starter project asks for a package that does not match the published one ("@wave/sdk": "^2.0.0" at templates/api-integration/package.json:11), so installing dependencies in a newly created project fails.
Impact: Projects created by the CLI cannot be installed or run out of the box.
Wrong scope on the SDK dependency in every template
The CLI itself depends on @wave-av/sdk (package.json:58), and the recovery notes discuss @wave-av/workflow-sdk, but all eight templates declare @wave/sdk and import from it: templates/api-integration/package.json:11, templates/blank/package.json:11, templates/multi-camera/package.json:11, templates/podcast/package.json:11, templates/srt-contribution/package.json:11, templates/studio-plugin/package.json:11, templates/webhook-handler/package.json:11, templates/webrtc-demo/package.json:11, plus the corresponding import { Wave } from "@wave/sdk" lines in each src/index.ts. wave init copies these files and then runs npm install (src/commands/init/index.ts:227-237), which throws with the npm stderr.
Prompt for agents
All templates under templates/ declare and import '@wave/sdk' while the CLI and the published ecosystem use '@wave-av/sdk' (see package.json dependencies). Because `wave init` copies a template and then runs `npm install` (src/commands/init/index.ts), scaffolded projects fail to install. Update the dependency name and the import statements in each template's package.json and src/index.ts to the correct scope, and verify the API surface used in each sample still matches the SDK.
Was this helpful? React with 👍 or 👎 to provide feedback.
| program | ||
| .name("wave") | ||
| .description("WAVE CLI - Command-line interface for the WAVE streaming platform") | ||
| .version("1.0.0", "-v, --version") |
There was a problem hiding this comment.
🟡 The CLI reports the wrong version number
The version reported to users is hardcoded to an old value (.version("1.0.0", "-v, --version") at src/cli.ts:82) while the package being shipped is 1.0.8, so wave --version misinforms anyone checking which release they have.
Impact: Bug reports and support requests will cite a version that does not match the installed release.
Three hardcoded 1.0.0 strings vs package.json 1.0.8
package.json:3 declares "version": "1.0.8". The literal 1.0.0 is hardcoded in the version flag (src/cli.ts:82), the banner (src/cli.ts:71), the raw-request User-Agent (src/commands/api/index.ts:34) and the SDK header X-Wave-CLI-Version (src/lib/api-client.ts:49), so server-side telemetry also attributes all traffic to 1.0.0. Reading the version from the manifest at build time would keep these in sync.
Was this helpful? React with 👍 or 👎 to provide feedback.
| async function getKeytar(): Promise<KeytarLike | null> { | ||
| if (keytarChecked) return keytarModule; | ||
| keytarChecked = true; | ||
| try { | ||
| keytarModule = (await import( | ||
| /* webpackIgnore: true */ "keytar" as string | ||
| )) as unknown as KeytarLike; | ||
| return keytarModule; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
🔍 Keytar is loaded via a dynamic import that may not expose named CJS exports
getKeytar() does (await import("keytar" as string)) as unknown as KeytarLike and then calls keytar.setPassword(...) directly on the module namespace. keytar is a CommonJS native addon; Node's cjs-module-lexer usually surfaces named exports for such modules, but for native .node-backed bindings the named-export detection can fail, in which case the functions live only under .default and keytar.setPassword would be undefined at runtime — which throws a TypeError rather than falling through to the file-based fallback (the try/catch only covers the import itself, not the call). Worth verifying against the actual keytar build before relying on it; a mod.default ?? mod normalisation would be safer.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const url = path.startsWith("http") | ||
| ? path | ||
| : `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`; | ||
|
|
||
| const headers: Record<string, string> = { | ||
| Authorization: `Bearer ${apiKey}`, | ||
| "Content-Type": "application/json", | ||
| "User-Agent": "wave-cli/1.0.0", | ||
| }; |
There was a problem hiding this comment.
🟨 Raw API command sends the stored bearer token to any absolute URL supplied on the command line
wave api <method> <path> accepts an absolute URL (path.startsWith("http") at src/commands/api/index.ts:27-29) and unconditionally attaches Authorization: Bearer ${apiKey} (src/commands/api/index.ts:31-35). A user pasting a command from an untrusted source, or a script/agent constructing the path from untrusted input, will transmit the account's long-lived API key to an arbitrary third-party host. The same code also permits plain http:// URLs, sending the credential in cleartext.
Was this helpful? React with 👍 or 👎 to provide feedback.
| function setNestedValue(obj: Record<string, unknown>, path: string, value: string): void { | ||
| const keys = path.split("."); | ||
| let current: Record<string, unknown> = obj; | ||
| for (let i = 0; i < keys.length - 1; i++) { | ||
| const key = keys[i]; | ||
| if (typeof current[key] !== "object" || current[key] === null) { | ||
| current[key] = {}; | ||
| } | ||
| current = current[key] as Record<string, unknown>; | ||
| } | ||
| const lastKey = keys[keys.length - 1]; | ||
| // Auto-parse booleans and numbers | ||
| if (value === "true") current[lastKey] = true; | ||
| else if (value === "false") current[lastKey] = false; | ||
| else if (!isNaN(Number(value)) && value !== "") current[lastKey] = Number(value); | ||
| else current[lastKey] = value; | ||
| } |
There was a problem hiding this comment.
🟨 Configuration setter allows writing to prototype keys
setNestedValue in src/commands/config/index.ts:78-94 splits a user-supplied dotted key and walks/creates nested objects with no key filtering, so wave config set __proto__.polluted x or wave config set constructor.prototype.x y writes onto Object.prototype for the remainder of the process and persists the crafted structure into ~/.wave/config.json, where it is re-parsed on every later invocation.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (envKey) { | ||
| checks.push({ | ||
| name: "Auth", | ||
| status: "pass", | ||
| message: `WAVE_API_KEY env var set (${envKey.slice(0, 12)}...)`, | ||
| }); | ||
| } else if (apiKey) { | ||
| checks.push({ | ||
| name: "Auth", | ||
| status: "pass", | ||
| message: `API key stored for "${config.currentProject}" (${apiKey.slice(0, 12)}...)`, | ||
| }); |
There was a problem hiding this comment.
🟨 Diagnostics command prints a prefix of the live API key to the terminal
wave doctor echoes the first 12 characters of the API key from the keychain and of WAVE_API_KEY (src/commands/doctor/index.ts:55-66). Diagnostic output is routinely pasted into issue trackers, chat and CI logs, so a meaningful prefix of a live credential ends up in places it should not be.
Was this helpful? React with 👍 or 👎 to provide feedback.
Code Review by Qodo
1. lint script missing eslint
|
| "test": "vitest run", | ||
| "test:watch": "vitest", | ||
| "lint": "eslint src/", | ||
| "prepublishOnly": "npm run build" |
There was a problem hiding this comment.
1. lint script missing eslint 📘 Rule violation ✧ Quality
package.json defines lint as eslint src/, but eslint is not listed in dependencies or devDependencies, so npm run lint will fail in CI on a clean install. This introduces a deterministic new CI failure risk for the configured lint command.
Agent Prompt
## Issue description
`npm run lint` is configured to run `eslint src/`, but the `eslint` package is not installed via `package.json`, causing lint to fail in CI/clean environments.
## Issue Context
Compliance requires that changed code must not introduce new lint/type/test failures in the existing CI configuration. Since the `lint` script invokes `eslint`, the repo should declare `eslint` (and any required config) so the command can run successfully.
## Fix Focus Areas
- package.json[16-23]
- package.json[71-79]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| await updateConfig((config) => ({ | ||
| ...config, | ||
| currentProject: project, | ||
| })); |
There was a problem hiding this comment.
2. Login leaves project unusable 🐞 Bug ≡ Correctness
auth login stores a credential and changes currentProject without creating the corresponding config.projects entry. On a fresh install, getClient() rejects that missing entry before reading the stored key, blocking SDK commands and even wave link.
Agent Prompt
## Issue description
Login stores credentials for a project that is absent from `config.projects`, so authenticated SDK commands immediately reject it.
## Issue Context
Both API-key and device-flow login paths must establish the project invariant expected by `getClient()`. Populate required organization fields from the authentication response where available, or allow a key-only default project safely.
## Fix Focus Areas
- src/commands/auth/index.ts[17-43]
- src/lib/api-client.ts[20-46]
- src/lib/config/schema.ts[3-20]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const thisFile = fileURLToPath(import.meta.url); | ||
| // Walk up from src/commands/init/ or dist/commands/init/ to package root | ||
| const packageRoot = resolve(dirname(thisFile), "..", "..", ".."); | ||
| return join(packageRoot, "templates"); |
There was a problem hiding this comment.
3. Bundled init misses templates 🐞 Bug ≡ Correctness
getTemplatesDir() walks up three levels as though the runtime file remains under dist/commands/init, but tsup emits the bundled entry as dist/index.js. Installed builds therefore resolve outside the package, skip the shipped template, and produce an incomplete fallback project without its package manifest or source file.
Agent Prompt
## Issue description
The runtime template path assumes an unbundled module layout and points outside the package after tsup bundles the CLI.
## Issue Context
Templates are shipped beside `dist` at the package root. Resolve assets relative to the actual bundled `dist/index.js` location or inject a stable package-root path, and fail clearly rather than creating an unusable partial scaffold.
## Fix Focus Areas
- src/commands/init/index.ts[52-79]
- src/commands/init/index.ts[145-168]
- tsup.config.ts[3-9]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const url = path.startsWith("http") | ||
| ? path | ||
| : `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`; |
There was a problem hiding this comment.
4. Api key sent off-host 🐞 Bug ⛨ Security
wave api accepts any absolute HTTP URL and unconditionally attaches the stored WAVE bearer token. Supplying an attacker-controlled URL therefore discloses the credential outside the configured API host.
Agent Prompt
## Issue description
The raw API command forwards the stored bearer token to arbitrary absolute URLs.
## Issue Context
Relative paths should resolve against the configured WAVE base URL. Reject off-host absolute URLs, or require explicit opt-in and omit credentials; also enforce an approved HTTPS scheme.
## Fix Focus Areas
- src/commands/api/index.ts[26-35]
- src/commands/api/index.ts[45-54]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| while (!controller.signal.aborted) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) break; |
There was a problem hiding this comment.
5. Clean sse eof hangs 🐞 Bug ☼ Reliability
A normal SSE response EOF breaks the reader loop without reconnecting, invoking onClose, or aborting the controller. listen and logs tail then remain blocked waiting only for an abort while the dead stream can deliver no more events.
Agent Prompt
## Issue description
Clean server-side stream closure bypasses all reconnect and close handling, leaving streaming commands hung.
## Issue Context
Treat EOF as a disconnect: either enter the bounded reconnect path or close/abort the controller so callers resolve. Ensure callbacks and signal-handler cleanup occur exactly once.
## Fix Focus Areas
- src/lib/sse-client.ts[94-164]
- src/commands/listen/index.ts[61-63]
- src/commands/logs/index.ts[85-87]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| "scripts": { | ||
| "dev": "npx tsx src/index.ts", | ||
| "build": "tsc" |
There was a problem hiding this comment.
7. Template env file ignored 🐞 Bug ≡ Correctness
Generated instructions tell users to place credentials in .env.local, but template dev scripts launch tsx without loading that file and the code only reads process.env. Following the generated README therefore starts templates with an undefined API key unless users separately export it.
Agent Prompt
## Issue description
Generated templates do not load the `.env.local` file that their generated README instructs users to create.
## Issue Context
Add an environment loader/preload to every template or change the scripts and documentation to use a supported env-file mechanism. Validate `npm run dev` using only the documented setup steps.
## Fix Focus Areas
- templates/blank/package.json[6-15]
- templates/blank/src/index.ts[3-5]
- src/commands/init/index.ts[184-209]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| console.error( | ||
| chalk.red( | ||
| `No project "${projectName}" configured. Run ${chalk.bold("wave login")} to authenticate.`, | ||
| ), |
There was a problem hiding this comment.
9. Authentication fixes are invalid 🐞 Bug ≡ Correctness
Multiple error and onboarding messages instruct users to run wave login, but the only registered login command is wave auth login. Following the displayed remediation produces an unknown-command error instead of restoring authentication.
Agent Prompt
## Issue description
Authentication remediation points to a top-level command that the CLI does not register.
## Issue Context
Either add a top-level `login` alias that delegates to `auth login`, or update all errors, suggestions, doctor fixes, and generated next steps to consistently use `wave auth login`.
## Fix Focus Areas
- src/commands/auth/index.ts[9-15]
- src/lib/api-client.ts[23-40]
- src/lib/errors.ts[43-49]
- src/commands/init/index.ts[242-250]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| async function saveCredentials(creds: Record<string, string>): Promise<void> { | ||
| const dir = join(homedir(), ".wave"); | ||
| if (!existsSync(dir)) { | ||
| await mkdir(dir, { recursive: true }); | ||
| } | ||
| await writeFile(CRED_FILE, JSON.stringify(creds, null, 2), { | ||
| mode: 0o600, | ||
| }); | ||
| } | ||
|
|
||
| async function storeToFile(project: string, key: string): Promise<void> { | ||
| const creds = await loadCredentials(); | ||
| creds[project] = key; | ||
| await saveCredentials(creds); | ||
| } |
There was a problem hiding this comment.
10. Plaintext credential fallback lacks hardened permissions 🐞 Bug ⛨ Security
When the optional native keytar module cannot be loaded, storeApiKey/getApiKey silently fall
back to writing bearer API keys as plaintext JSON in ~/.wave/credentials.json. The containing
directory is created with mkdir(dir, { recursive: true }) without an explicit restrictive mode
(left to the process umask) and no warning is emitted to the user that credentials are stored
unencrypted on disk.
Agent Prompt
## Issue description
When the optional `keytar` native module cannot be loaded, the CLI falls back to storing API keys as plaintext JSON at `~/.wave/credentials.json`. The fallback directory is created without an explicit restrictive mode, and the user is never told their credentials are stored unencrypted.
## Issue Context
This is a newly added fallback path introduced in this PR (`src/lib/auth/keychain.ts`). It is exercised whenever the dynamic `import("keytar")` throws (e.g., missing native binary, unsupported platform), which is a realistic scenario for many CLI users.
## Fix Focus Areas
- src/lib/auth/keychain.ts[82-89] (saveCredentials: harden directory creation with `mkdir(dir, { recursive: true, mode: 0o700 })` and consider explicitly `chmod`-ing an already-existing directory/file to 0o700/0o600)
- src/lib/auth/keychain.ts[26-33] (storeApiKey: emit a one-time warning to stderr when falling back to file storage instead of the OS keychain)
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| "dependencies": { | ||
| "@wave/sdk": "^2.0.0", | ||
| "express": "^4.21.0" |
There was a problem hiding this comment.
11. Templates depend on non-existent @wave/sdk package 🐞 Bug ≡ Correctness
All eight scaffold templates (webrtc-demo, srt-contribution, webhook-handler, api-integration, studio-plugin, blank, multi-camera, podcast) import and depend on @wave/sdk, but the CLI, documentation, and generated configuration identify the SDK as @wave-av/sdk. As a result, running wave init with any of these templates and then npm install will fail to resolve the intended dependency or may install an unrelated package, breaking the generated project out of the box.
Agent Prompt
## Issue description
All scaffold template projects reference `@wave/sdk` in their `package.json` dependencies and source imports instead of the intended/published `@wave-av/sdk` package used by the CLI, docs, and generated `wave.config.ts`. This mismatch causes `npm install` to fail (package not found) or to install an incorrect/unrelated package, leaving projects generated by `wave init` broken by default.
## Issue Context
The CLI’s own dependencies and code paths already use `@wave-av/sdk` (including `package.json` and files like `src/lib/api-client.ts`, `src/lib/errors.ts`), and `wave init` copies `templates/*` verbatim into the user’s new project (see `src/commands/init/index.ts`). Update every template manifest and source import to consistently use `@wave-av/sdk`, then install and type-check each generated template to confirm everything resolves correctly.
## Fix Focus Areas
- templates/webhook-handler/package.json[10-12]
- templates/webhook-handler/src/index.ts[1-9]
- templates/api-integration/package.json[10-12]
- templates/blank/package.json[10-12]
- templates/blank/package.json[10-15]
- templates/blank/src/index.ts[1-4]
- templates/multi-camera/package.json[10-12]
- templates/podcast/package.json[10-12]
- templates/srt-contribution/package.json[10-12]
- templates/studio-plugin/package.json[10-12]
- templates/webrtc-demo/package.json[10-12]
- templates/webhook-handler/package.json[10-17]
- templates/webhook-handler/src/index.ts[1-5]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
|
||
| import open from "open"; | ||
| import chalk from "chalk"; | ||
| import type { DeviceAuthResponse, TokenResponse } from "../../types/index.js"; |
There was a problem hiding this comment.
12. Recovered auth/config code imports missing types module 🐞 Bug ≡ Correctness
src/lib/auth/device-flow.ts, src/lib/config/manager.ts, and src/lib/output/index.ts import type declarations from ../../types/index.js / src/types/index.js, but no src/types module exists in the recovered source tree. As a result, tsc --noEmit / tsup (and thus `npm run type-check / npm run build`) fail on unresolved module imports, undermining the PR’s reproducible, buildable recovery claim.
Agent Prompt
## Issue description
Several recovered source files import type-only symbols from a shared `types/index` module (e.g., `DeviceAuthResponse`, `TokenResponse`, `WaveConfig`, `OutputFormat`), but the repository does not contain the corresponding `src/types` tree/module, causing TypeScript module resolution failures during `tsc --noEmit`/`tsup`.
## Issue Context
This breaks the PR’s core reproducible-build assertion: the documented verification steps in `RECOVERY.md` (`npm ci --include=dev && npx tsup`) and the added/available `npm run type-check` rely on `tsconfig` including all `src` files and on every import resolving successfully. To restore buildability, either add the missing `src/types/index.ts` (or equivalent) exporting the required types, or re-point these imports to existing sources (schemas/modules) that already define them, then confirm the full repo passes the type-check script.
## Fix Focus Areas
- src/lib/auth/device-flow.ts[11-11]
- src/lib/config/manager.ts[5-6]
- src/lib/output/index.ts[1-1]
- tsconfig.json[11-14]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Qodo Fixer🍒 Ready to be cherry-picked — ✅ Merged (0) · ☑ Fixed (7) 🔗 Fix PR: #19 This fix PR was closed automatically. Its branch is preserved so you can cherry pick the changes into the original PR. Prompt for coding agent Process — 7 fixed
|
This repo advertises itself as the source of
@wave-av/cliand contains none of it. Eight versions were published between 2026-04-03 and 2026-08-04 from a working copy that was never committed — so a stranger who runsnpm install @wave-av/cliand follows therepositorylink the package itself carries arrives at a README and a LICENSE, while the code executing on their machine exists in no public repository.The source was never lost — it ships in every tarball
Every published version carries
dist/index.js.map, and that sourcemap carriessourcesContent: not the names of the original files but their complete contents, pre-compilation. All 70 TypeScript files in this PR were extracted from it verbatim. Nothing was inferred from the compiled bundle, hand-written to match, or reasoned backwards from the type declarations.The receipt — every version, not a sample
dist/index.jsvs publishedcmpexits 0 silently on all eight.RECOVERY.mdcarries the three commands to re-derive any row from scratch.This is a reproducible-build proof, which is a stronger claim than the recovery bar asked for — that bar was a matching file list; this is the matching bundle, byte for byte.
What was AUTHORED, and is therefore not recovered
Two files, called out here rather than left looking like they came out of the artifact, because a sourcemap contains source and not build configuration:
tsconfig.json— conventional strict ES2022/ESNexttsup.config.ts— entrysrc/index.ts, ESM, node18, sourcemap, shebang bannerThey are not loose guesses: they are the settings under which output matches the published bytes exactly, eight times over. A different plausible configuration would have produced a different bundle and every comparison above would have failed. But written is not recovered, and conflating those two is the exact class of error this exercise exists to correct.
package.jsonis the published manifest, which npm preserves in full includingscriptsanddevDependencies.Safety
gitleakswith this repo's own.gitleaks.toml: no leaks found, 251 KB scanned. Worth stating why that check came first — the sourcemap has been publicly downloadable since April, so a credential inside it would have been a live four-month exposure, a fact about the published package rather than a risk created by recovering it. It is clean.What this unblocks, and what it deliberately does not
Unblocks #17. That publish workflow is correct and cannot run: its
verifyjob dies atnpm ciagainst a repo with nopackage.json. The workflow was never the blocker; the missing source was. Do not weaken #17 to make it green — merge this first.Does not create the tags. Recovering source does not retroactively produce the tags these eight versions never had. Tagging each against its own
cmpreceipt is tracked separately, and tags on a public repo are a claim that this commit produced that artifact — checkable, permanent, and worse than no tag if wrong.Says nothing about
@wave-av/workflow-sdk. Its seven versions carry no sourcemap at all. That source is recoverable only as a reconstruction — a tree provably producing the published bytes, which is genuinely weaker than a tree that did produce them. Both end with a repo that builds and publishes identical output; only one is true, and they must not be recorded as the same thing.Refs wave-av/claude-workstation#1624
Note
Cursor Bugbot is generating a summary for commit 8ce183a. Configure here.
Note
Add Wave CLI source recovered from published sourcemaps with full command set
src/.@wave/sdkclient.package.jsonand.env.examplefiles.Macroscope summarized 8ce183a.