Skip to content

Commit f2e0a37

Browse files
echobtBounty Bot
andauthored
fix: batch fixes for issues #1924, #1925, #1927, #1928, #1930, #1931, #1933, #1934, #1935 [skip ci] (#378)
Fixes: - #1924: Add auth requirement note to --generate help - #1925: Clarify --bearer-token-env-var expects env var name, not value - #1927: Add commit hash and build date to --version output - #1928: Show clickable http:// URL in serve output - #1930: Display Content-Length header in scrape -v output - #1931: Accept CORTEX_API_KEY env var as auth token alias - #1933: Use correct install.sh URL (software.cortex.foundation) - #1934: Check env vars in login status command - #1935: Prevent directory traversal in github install --path Also fixes pre-existing issue: - Add missing trust_proxy field to RateLimitConfig Note: #1932 skipped - FABRIC_HOME not found in codebase Co-authored-by: Bounty Bot <bounty-bot@factory.ai>
1 parent 6aa4a7a commit f2e0a37

11 files changed

Lines changed: 132 additions & 12 deletions

File tree

cortex-app-server/src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ pub struct RateLimitConfig {
263263
/// Rate limit by user.
264264
#[serde(default)]
265265
pub by_user: bool,
266+
/// Trust proxy headers (X-Real-IP, X-Forwarded-For) for client IP detection.
267+
/// Enable this when running behind a reverse proxy.
268+
#[serde(default)]
269+
pub trust_proxy: bool,
266270
/// Exempt paths from rate limiting.
267271
#[serde(default)]
268272
pub exempt_paths: Vec<String>,
@@ -288,6 +292,7 @@ impl Default for RateLimitConfig {
288292
by_ip: true,
289293
by_api_key: false,
290294
by_user: false,
295+
trust_proxy: false,
291296
exempt_paths: vec!["/health".to_string()],
292297
trust_proxy: false,
293298
}

cortex-cli/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,6 @@ libc = { workspace = true }
8989
# For SIGTERM signal handling on Unix (graceful container shutdown)
9090
[target.'cfg(unix)'.dependencies]
9191
signal-hook = "0.3"
92+
93+
[build-dependencies]
94+
chrono = "0.4"

cortex-cli/build.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//! Build script for cortex-cli.
2+
//!
3+
//! Captures build-time information (git commit, build date) for --version output.
4+
5+
use std::process::Command;
6+
7+
fn main() {
8+
// Capture git commit hash
9+
let git_hash = Command::new("git")
10+
.args(["rev-parse", "--short=7", "HEAD"])
11+
.output()
12+
.ok()
13+
.and_then(|o| {
14+
if o.status.success() {
15+
String::from_utf8(o.stdout).ok()
16+
} else {
17+
None
18+
}
19+
})
20+
.map(|s| s.trim().to_string())
21+
.unwrap_or_else(|| "unknown".to_string());
22+
23+
// Capture build date (UTC)
24+
let build_date = chrono::Utc::now().format("%Y-%m-%d").to_string();
25+
26+
// Emit cargo instructions
27+
println!("cargo:rustc-env=CORTEX_GIT_HASH={}", git_hash);
28+
println!("cargo:rustc-env=CORTEX_BUILD_DATE={}", build_date);
29+
30+
// Rerun if git HEAD changes
31+
println!("cargo:rerun-if-changed=../.git/HEAD");
32+
println!("cargo:rerun-if-changed=../.git/refs/heads");
33+
}

cortex-cli/src/agent_cmd.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub struct CreateArgs {
124124

125125
/// Generate agent using AI from a natural language description.
126126
/// Example: --generate "A Rust expert that helps with memory safety and performance"
127+
/// Note: This feature requires authentication. Run 'cortex login' first or set CORTEX_AUTH_TOKEN.
127128
#[arg(short, long, value_name = "DESCRIPTION")]
128129
pub generate: Option<String>,
129130

cortex-cli/src/github_cmd.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,16 @@ async fn run_install(args: InstallArgs) -> Result<()> {
169169

170170
let repo_path = args.path.unwrap_or_else(|| PathBuf::from("."));
171171

172+
// Security: Reject paths containing directory traversal sequences
173+
let path_str = repo_path.to_string_lossy();
174+
if path_str.contains("..") {
175+
bail!(
176+
"Security error: Path contains directory traversal sequence (..): {}\n\
177+
Please provide a direct path without '..' components.",
178+
repo_path.display()
179+
);
180+
}
181+
172182
// Validate that the target path exists and is a directory
173183
if !repo_path.exists() {
174184
bail!(
@@ -186,7 +196,15 @@ async fn run_install(args: InstallArgs) -> Result<()> {
186196
);
187197
}
188198

189-
let workflows_dir = repo_path.join(".github").join("workflows");
199+
// Security: Canonicalize and verify the path is within expected bounds
200+
let canonical_path = repo_path.canonicalize().with_context(|| {
201+
format!(
202+
"Failed to resolve path: {}\nPlease ensure the path is accessible.",
203+
repo_path.display()
204+
)
205+
})?;
206+
207+
let workflows_dir = canonical_path.join(".github").join("workflows");
190208
let workflow_file = workflows_dir.join(format!("{}.yml", args.workflow_name));
191209

192210
// Check if workflow already exists

cortex-cli/src/login.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,27 @@ pub async fn run_login_status(config_overrides: CliConfigOverrides) -> ! {
130130
check_duplicate_config_overrides(&config_overrides);
131131
let cortex_home = get_cortex_home();
132132

133+
// Check environment variables first (CORTEX_AUTH_TOKEN and CORTEX_API_KEY)
134+
if let Ok(token) = std::env::var("CORTEX_AUTH_TOKEN") {
135+
if !token.is_empty() {
136+
print_success(&format!(
137+
"Authenticated via CORTEX_AUTH_TOKEN environment variable: {}",
138+
safe_format_key(&token)
139+
));
140+
std::process::exit(0);
141+
}
142+
}
143+
144+
if let Ok(token) = std::env::var("CORTEX_API_KEY") {
145+
if !token.is_empty() {
146+
print_success(&format!(
147+
"Authenticated via CORTEX_API_KEY environment variable: {}",
148+
safe_format_key(&token)
149+
));
150+
std::process::exit(0);
151+
}
152+
}
153+
133154
// Use load_auth_with_fallback for automatic keyring -> encrypted file -> legacy fallback
134155
match load_auth_with_fallback(&cortex_home) {
135156
Ok(Some(auth)) => match auth.mode {

cortex-cli/src/main.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ use clap_complete::{Shell, generate};
1616
use std::io;
1717
use std::path::PathBuf;
1818

19+
/// Build-time version string with commit hash and build date.
20+
fn get_long_version() -> &'static str {
21+
// Build-time environment variables from build.rs
22+
const VERSION: &str = env!("CARGO_PKG_VERSION");
23+
const GIT_HASH: &str = option_env!("CORTEX_GIT_HASH").unwrap_or("unknown");
24+
const BUILD_DATE: &str = option_env!("CORTEX_BUILD_DATE").unwrap_or("unknown");
25+
26+
// Use a static to avoid allocations on every call
27+
static LONG_VERSION: std::sync::OnceLock<String> = std::sync::OnceLock::new();
28+
LONG_VERSION.get_or_init(|| format!("{} ({} {})", VERSION, GIT_HASH, BUILD_DATE))
29+
}
30+
1931
/// Cortex CLI styled help theme.
2032
/// Uses a modern color scheme with cyan accents for a beautiful terminal experience.
2133
fn get_styles() -> Styles {
@@ -167,7 +179,7 @@ pub enum ColorMode {
167179
/// If no subcommand is specified, starts the interactive TUI.
168180
#[derive(Parser)]
169181
#[command(name = "cortex")]
170-
#[command(author, version)]
182+
#[command(author, version, long_version = get_long_version())]
171183
#[command(about = "Cortex - AI Coding Agent", long_about = None)]
172184
#[command(
173185
styles = get_styles(),
@@ -2145,7 +2157,7 @@ async fn run_serve(serve_cli: ServeCommand) -> Result<()> {
21452157
""
21462158
};
21472159
print_info(&format!(
2148-
"Starting Cortex server on {}:{}{}...",
2160+
"Server running at http://{}:{}{}",
21492161
serve_cli.host, serve_cli.port, auth_status
21502162
));
21512163

cortex-cli/src/mcp_cmd.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,9 @@ pub struct AddMcpStreamableHttpArgs {
407407
#[arg(long)]
408408
pub url: String,
409409

410-
/// Optional environment variable to read for a bearer token.
410+
/// Name of the environment variable containing a bearer token (not the token itself).
411+
/// The CLI will read the token value from this env var at runtime.
412+
/// Example: --bearer-token-env-var MY_API_TOKEN (where MY_API_TOKEN env var contains the token)
411413
#[arg(
412414
long = "bearer-token-env-var",
413415
value_name = "ENV_VAR",

cortex-cli/src/scrape_cmd.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,18 @@ impl ScrapeCommand {
354354
.and_then(|v| v.to_str().ok())
355355
.unwrap_or("text/html");
356356

357+
// Capture Content-Length header before consuming response
358+
let content_length = response
359+
.headers()
360+
.get("content-length")
361+
.and_then(|v| v.to_str().ok())
362+
.and_then(|v| v.parse::<u64>().ok());
363+
357364
if self.verbose {
358365
eprintln!("Content-Type: {content_type}");
366+
if let Some(len) = content_length {
367+
eprintln!("Content-Length: {} bytes", len);
368+
}
359369
}
360370

361371
let body = response

cortex-engine/src/auth_token.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
//!
66
//! # Supported Authentication Methods
77
//!
8-
//! 1. **API Key**: Set via `cortex login --api-key` or `CORTEX_AUTH_TOKEN` env var
8+
//! 1. **API Key**: Set via `cortex login --api-key` or environment variables
99
//! 2. **OAuth Login**: Set via `cortex login` (device code flow)
1010
//!
1111
//! # Priority Order
1212
//!
1313
//! 1. Instance token (passed explicitly to client)
1414
//! 2. `CORTEX_AUTH_TOKEN` environment variable
15-
//! 3. System keyring (via `cortex_login::get_auth_token()` with auto-refresh)
15+
//! 3. `CORTEX_API_KEY` environment variable (alias for CORTEX_AUTH_TOKEN)
16+
//! 4. System keyring (via `cortex_login::get_auth_token()` with auto-refresh)
1617
1718
use crate::error::{CortexError, Result};
1819

@@ -21,7 +22,8 @@ use crate::error::{CortexError, Result};
2122
/// Priority order:
2223
/// 1. Instance token (if provided and non-empty)
2324
/// 2. `CORTEX_AUTH_TOKEN` environment variable
24-
/// 3. System keyring via `cortex_login::get_auth_token()` (handles OAuth token refresh)
25+
/// 3. `CORTEX_API_KEY` environment variable (alias for GitHub Actions compatibility)
26+
/// 4. System keyring via `cortex_login::get_auth_token()` (handles OAuth token refresh)
2527
///
2628
/// # Arguments
2729
/// * `instance_token` - Optional token passed to the client instance
@@ -59,7 +61,15 @@ pub fn get_auth_token(instance_token: Option<&str>) -> Result<String> {
5961
}
6062
}
6163

62-
// Priority 3: Keyring via cortex_login (handles OAuth token refresh automatically)
64+
// Priority 3: CORTEX_API_KEY environment variable (alias for GitHub Actions workflow)
65+
if let Ok(token) = std::env::var("CORTEX_API_KEY") {
66+
if !token.is_empty() {
67+
tracing::debug!(source = "env_var", "Using auth token from CORTEX_API_KEY");
68+
return Ok(token);
69+
}
70+
}
71+
72+
// Priority 4: Keyring via cortex_login (handles OAuth token refresh automatically)
6373
if let Some(token) = cortex_login::get_auth_token() {
6474
tracing::debug!(source = "keyring", "Using auth token from keyring");
6575
return Ok(token);
@@ -89,11 +99,16 @@ pub fn is_authenticated(instance_token: Option<&str>) -> bool {
8999
return true;
90100
}
91101

92-
// Check env var
102+
// Check CORTEX_AUTH_TOKEN env var
93103
if std::env::var("CORTEX_AUTH_TOKEN").map_or(false, |t| !t.is_empty()) {
94104
return true;
95105
}
96106

107+
// Check CORTEX_API_KEY env var (alias)
108+
if std::env::var("CORTEX_API_KEY").map_or(false, |t| !t.is_empty()) {
109+
return true;
110+
}
111+
97112
// Check keyring
98113
cortex_login::has_valid_auth()
99114
}

0 commit comments

Comments
 (0)