-
Notifications
You must be signed in to change notification settings - Fork 0
feat(cli): Basic CLI commands (Spec 008-cli) #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add CLI app with gateway, config, channels, and pairing commands. This completes Milestone 1 of the MVP. Commands added: - finna gateway run/status - Gateway management - finna config show/get/set/unset - Configuration management - finna channels list/status - Channel listing and connectivity - finna pairing list/approve/reject - DM pairing flow Also adds config module to @finna/core for reading/writing ~/.finna/config.json configuration file.
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| const url = `ws://${host}:${port}`; | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| const ws = new WebSocket(url); |
Check warning
Code scanning / CodeQL
File data in outbound network request Medium
file data
|
|
||
| ws.onopen = () => { | ||
| if (token) { | ||
| ws.send(JSON.stringify({ type: 'auth', token })); |
Check warning
Code scanning / CodeQL
File data in outbound network request Medium
file data
| let current: Record<string, unknown> = config; | ||
| for (const part of parts) { | ||
| if (current[part] === undefined || typeof current[part] !== 'object') { | ||
| current[part] = {}; |
Check warning
Code scanning / CodeQL
Prototype-polluting assignment Medium
library input
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 15 days ago
In general, to fix prototype-polluting assignments arising from user-controlled property names, either (1) use data structures that are not prototype-based (like Map), or (2) ensure that untrusted keys cannot be special prototype-related names (__proto__, constructor, prototype), by rejecting or transforming them before using them as object keys.
For this specific code, the minimal, non-breaking fix is to validate each part (and the final lastKey) before using it as a property name. If a path segment is one of the dangerous keys, we should reject the path (throwing an error) instead of creating/using that property. This keeps the existing behavior for all legitimate keys and avoids changing the shape or type of config. Concretely:
- Add a small helper function in this file (e.g.,
isUnsafeKey) that checks if a key is'__proto__','constructor', or'prototype'. - In
setConfigValue, inside thefor (const part of parts)loop, before accessingcurrent[part]or assigning to it, checkisUnsafeKey(part)and throw an error if it’s unsafe. - After computing
lastKey, validate it in the same way before using it indelete current[lastKey]orcurrent[lastKey] = value.
This requires no new imports; the helper can be a simple local function.
-
Copy modified lines R30-R33 -
Copy modified lines R84-R86 -
Copy modified lines R93-R96
| @@ -27,6 +27,10 @@ | ||
|
|
||
| let cachedConfig: FinnaConfig | null = null; | ||
|
|
||
| function isUnsafeKey(key: string): boolean { | ||
| return key === '__proto__' || key === 'constructor' || key === 'prototype'; | ||
| } | ||
|
|
||
| export async function getConfig(): Promise<FinnaConfig> { | ||
| if (cachedConfig) { | ||
| return cachedConfig; | ||
| @@ -77,12 +81,19 @@ | ||
|
|
||
| let current: Record<string, unknown> = config; | ||
| for (const part of parts) { | ||
| if (isUnsafeKey(part)) { | ||
| throw new Error(`Invalid config path segment: ${part}`); | ||
| } | ||
| if (current[part] === undefined || typeof current[part] !== 'object') { | ||
| current[part] = {}; | ||
| } | ||
| current = current[part] as Record<string, unknown>; | ||
| } | ||
|
|
||
| if (isUnsafeKey(lastKey)) { | ||
| throw new Error(`Invalid config path segment: ${lastKey}`); | ||
| } | ||
|
|
||
| if (value === undefined) { | ||
| delete current[lastKey]; | ||
| } else { |
| } | ||
|
|
||
| if (value === undefined) { | ||
| delete current[lastKey]; |
Check warning
Code scanning / CodeQL
Prototype-polluting assignment Medium
library input
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 15 days ago
In general, to fix this kind of issue you must prevent user‑controlled strings from being used as raw property keys on ordinary objects in a way that could touch the prototype chain. Typical strategies are: (1) using a safe key‑value structure like Map instead of plain objects, or (2) rejecting or transforming dangerous keys such as __proto__, constructor, and prototype before using them as property names.
For this codebase, the minimal, non‑breaking change is to validate each path segment (part) and the final key (lastKey) before using them as object property names. If any segment is one of the dangerous names, we should reject the path with a clear error rather than silently modifying Object.prototype. This preserves the existing data model (plain objects backed by JSON) and behavior for all legitimate keys, while blocking only clearly unsafe ones.
Concretely in packages/core/src/config/index.ts:
- Add a small helper function, e.g.
isUnsafeConfigKey(key: string): boolean, that returns true for keys__proto__,constructor, andprototype. - Use this helper:
- In
getConfigValue, before indexing with eachpart, returnundefinedif an unsafe key is encountered. - In
setConfigValue, after computingpartsandlastKey, check each segment inpartsandlastKey; if any is unsafe, throw anError('Invalid config path: unsafe key segment').
- In
- Keep using plain objects and the existing file I/O; no new imports are needed.
This ensures that none of the property reads or writes in this file can ever target Object.prototype, eliminating the prototype pollution vector without altering legitimate configuration behavior.
-
Copy modified lines R30-R33 -
Copy modified lines R64-R66 -
Copy modified lines R85-R88
| @@ -27,6 +27,10 @@ | ||
|
|
||
| let cachedConfig: FinnaConfig | null = null; | ||
|
|
||
| function isUnsafeConfigKey(key: string): boolean { | ||
| return key === '__proto__' || key === 'constructor' || key === 'prototype'; | ||
| } | ||
|
|
||
| export async function getConfig(): Promise<FinnaConfig> { | ||
| if (cachedConfig) { | ||
| return cachedConfig; | ||
| @@ -57,6 +61,9 @@ | ||
|
|
||
| let current: unknown = config; | ||
| for (const part of parts) { | ||
| if (isUnsafeConfigKey(part)) { | ||
| return undefined; | ||
| } | ||
| if (current === null || current === undefined || typeof current !== 'object') { | ||
| return undefined; | ||
| } | ||
| @@ -75,6 +82,10 @@ | ||
| throw new Error('Invalid config path'); | ||
| } | ||
|
|
||
| if (isUnsafeConfigKey(lastKey) || parts.some(isUnsafeConfigKey)) { | ||
| throw new Error('Invalid config path: unsafe key segment'); | ||
| } | ||
|
|
||
| let current: Record<string, unknown> = config; | ||
| for (const part of parts) { | ||
| if (current[part] === undefined || typeof current[part] !== 'object') { |
| if (value === undefined) { | ||
| delete current[lastKey]; | ||
| } else { | ||
| current[lastKey] = value; |
Check warning
Code scanning / CodeQL
Prototype-polluting assignment Medium
library input
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 15 days ago
In general, to fix prototype-polluting assignments, you must ensure that untrusted strings are never used directly as property names on objects that inherit from Object.prototype. This can be done either by (1) switching to a data structure that does not share Object.prototype (like Map or Object.create(null)), or (2) validating and rejecting dangerous property names such as __proto__, constructor, and prototype.
For this code, the least invasive fix that preserves existing behavior is to keep using plain objects but validate every path segment (part) and the final lastKey before using them as property names. If any segment is __proto__, constructor, or prototype, we should reject the path by throwing an error rather than creating or assigning into that property. This directly prevents writes to these special keys and eliminates the prototype pollution vector while keeping the config shape and JSON serialization intact.
Concretely, in packages/core/src/config/index.ts:
- Add a small helper (or inline checks) to validate keys.
- In the loop over
partsinsetConfigValue, before accessingcurrent[part], check thatpartis not one of the forbidden keys. If it is, throw an error likeInvalid config path segment. - After computing
lastKey, validate it similarly before using it indelete current[lastKey]orcurrent[lastKey] = value. - Optionally, apply the same validation in
getConfigValueso reads cannot access those properties either; this keeps behavior consistent and avoids ever traversing through polluted prototypes.
No new imports are required; this can be implemented with simple string comparisons.
-
Copy modified lines R58-R59 -
Copy modified lines R62-R64 -
Copy modified lines R81-R85 -
Copy modified lines R88-R90
| @@ -55,8 +55,13 @@ | ||
| const config = await getConfig(); | ||
| const parts = path.split('.'); | ||
|
|
||
| const forbiddenKeys = new Set(['__proto__', 'constructor', 'prototype']); | ||
|
|
||
| let current: unknown = config; | ||
| for (const part of parts) { | ||
| if (forbiddenKeys.has(part)) { | ||
| throw new Error('Invalid config path segment'); | ||
| } | ||
| if (current === null || current === undefined || typeof current !== 'object') { | ||
| return undefined; | ||
| } | ||
| @@ -75,8 +78,16 @@ | ||
| throw new Error('Invalid config path'); | ||
| } | ||
|
|
||
| const forbiddenKeys = new Set(['__proto__', 'constructor', 'prototype']); | ||
| if (forbiddenKeys.has(lastKey)) { | ||
| throw new Error('Invalid config path segment'); | ||
| } | ||
|
|
||
| let current: Record<string, unknown> = config; | ||
| for (const part of parts) { | ||
| if (forbiddenKeys.has(part)) { | ||
| throw new Error('Invalid config path segment'); | ||
| } | ||
| if (current[part] === undefined || typeof current[part] !== 'object') { | ||
| current[part] = {}; | ||
| } |
Add comprehensive CLI documentation: - cli/overview.md - CLI overview and global options - cli/gateway.md - Gateway commands - cli/config.md - Config commands - cli/channels.md - Channel commands - cli/pairing.md - Pairing commands Fix CodeQL security warnings in file storage driver: - Use atomic file creation with 'wx' flag to fix TOCTOU race - Add prototype pollution protection to setNestedValue/getNestedValue
| if (!(part in current) || typeof current[part] !== 'object') { | ||
| current[part] = {} | ||
| if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object' || current[part] === null) { | ||
| current[part] = Object.create(null) |
Check warning
Code scanning / CodeQL
Prototype-polluting assignment Medium
library input
Copilot Autofix
AI 15 days ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
Code ReviewI found 3 issues that need attention: 1. Missing Test Coverage (CLAUDE.md Violation)Location: Multiple files across the PR This PR adds 777 lines of new production code across 7 files without any test coverage:
This violates the CLAUDE.md Testing guidelines:
Recommendation: Add colocated test files (
2. Documentation Uses pnpm Instead of bun (CLAUDE.md Violation)Location: The documentation shows
Current code: pnpm install
pnpm --filter cli buildSuggested fix: pnpm install
bun run --filter cli build3. Prototype Pollution Vulnerability in setConfigValue (Security)Location: The finna config set __proto__.polluted trueThis is inconsistent with the prototype pollution protection added to the file storage driver in this same PR ( Recommended fix: Add the same const FORBIDDEN_KEYS = ["__proto__", "constructor", "prototype"];
export async function setConfigValue(path: string, value: unknown): Promise<void> {
const config = await getConfig();
const parts = path.split(".");
// Validate all parts against forbidden keys
for (const part of parts) {
if (FORBIDDEN_KEYS.includes(part)) {
throw new Error(`Forbidden config key: ` + part);
}
}
const lastKey = parts.pop();
if (\!lastKey) throw new Error("Invalid config path");
let current: Record<string, unknown> = config;
for (const part of parts) {
if (\!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] \!== "object") {
current[part] = {};
}
current = current[part] as Record<string, unknown>;
}
current[lastKey] = value;
await saveConfig(config);
clearConfigCache();
} |
Summary
@finna/clipackage with Commander.js@finna/corefor configuration managementCommands Added
finna gateway run/status- Gateway server managementfinna config show/get/set/unset- Configuration managementfinna channels list/status- Channel listing and connectivityfinna pairing list/approve/reject- DM pairing flowTest plan
finna --helpshows all commandsfinna config set/get/showworks correctly