Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
41614c7
fix(security): close 2 HIGH findings from KCode self-audit (round 2)
GaltRanch Apr 12, 2026
2b30d86
feat(audit): exploit proof-of-concept generator for confirmed findings
GaltRanch Apr 12, 2026
ee58f36
fix(audit): py-001-eval-exec verify_prompt now recognizes safe exec()…
GaltRanch Apr 12, 2026
634c524
feat(audit): bespoke fixer for py-013-bare-except
GaltRanch Apr 12, 2026
273bbd7
feat(audit): CWE Top 25 100% coverage + 8 new exploit templates
GaltRanch Apr 12, 2026
4e9183b
feat(audit): 100% security domain coverage — 8 more CWE patterns
GaltRanch Apr 12, 2026
8cc5540
fix(security): close 3 findings from KCode self-audit (round 3)
GaltRanch Apr 12, 2026
1b8ba37
fix(audit): swift-003-insecure-http recognizes template/widget placeh…
GaltRanch Apr 12, 2026
da08ea2
fix(audit): js-014-json-parse-no-catch checks enclosing try scope + s…
GaltRanch Apr 12, 2026
e05bc19
feat(cloud): add xAI (Grok) as a cloud provider
GaltRanch Apr 12, 2026
01d62e2
feat(cloud): expand xAI model catalog with grok-4.20-reasoning as def…
GaltRanch Apr 12, 2026
66ef838
feat(cloud): show pricing per provider in the /cloud modal
GaltRanch Apr 12, 2026
e40f421
fix(cloud): strip bracketed-paste markers from pasted API keys
GaltRanch Apr 12, 2026
01034a7
feat(ui): show running USD cost in the Kodi companion panel
GaltRanch Apr 12, 2026
75921c7
fix(llm): reasoning models get 32K-token floor + reasoning_effort param
GaltRanch Apr 12, 2026
4e102f7
fix(cloud): xAI baseUrl normalization + strict reasoning_effort scope
GaltRanch Apr 12, 2026
9db995d
fix(level1): dev-server detection no longer falls through to python h…
GaltRanch Apr 12, 2026
d8e4123
feat(dev-server): port floor 11000, auto-start verification, show sta…
GaltRanch Apr 12, 2026
e3614ec
fix(dev-server): static-HTML detection + compacter system prompt rules
GaltRanch Apr 12, 2026
d91b2ed
feat(agents): Phase 1 — agent factory backend (names, pool, roles, na…
GaltRanch Apr 13, 2026
0852fc6
feat(agents): Phase 3a — executor, /agents and /agent slash commands
GaltRanch Apr 13, 2026
d61e729
feat(agents): Phase 3b — natural-language dispatch + system prompt in…
GaltRanch Apr 13, 2026
47e7543
feat(agents): Phase 2 — live AgentPanel TUI component
GaltRanch Apr 13, 2026
2c6a9db
fix: close 4 HIGH + 1 MEDIUM findings from branch self-audit
GaltRanch Apr 13, 2026
ca7b901
fix: close remaining MEDIUM + LOW findings from branch self-audit
GaltRanch Apr 13, 2026
c5be74c
fix: close findings from audit-cleanup self-review (v2.10.37+38 audit)
GaltRanch Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions mobile-ios/Models/AppSettings.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,47 @@
// KCodeMobile - Persistent settings
// Server URL, session ID, model preferences — persisted to UserDefaults
// Non-sensitive prefs (serverURL, model, cwd) use UserDefaults.
// Sensitive auth tokens (sessionId) use Keychain via Security framework.

import Foundation
import Security
import SwiftUI

private enum KeychainHelper {
static func save(key: String, value: String) {
guard let data = value.data(using: .utf8) else { return }
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
]
SecItemDelete(query as CFDictionary)
var add = query
add[kSecValueData as String] = data
add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
SecItemAdd(add as CFDictionary, nil)
}

static func load(key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
]
var result: AnyObject?
guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,
let data = result as? Data else { return nil }
return String(data: data, encoding: .utf8)
}

static func delete(key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
]
SecItemDelete(query as CFDictionary)
}
}

class AppSettings: ObservableObject {
@Published var serverURL: String {
didSet { UserDefaults.standard.set(serverURL, forKey: "serverURL") }
Expand All @@ -18,14 +56,26 @@ class AppSettings: ObservableObject {
}

@Published var sessionId: String? {
didSet { UserDefaults.standard.set(sessionId, forKey: "sessionId") }
didSet {
if let id = sessionId {
KeychainHelper.save(key: "sessionId", value: id)
} else {
KeychainHelper.delete(key: "sessionId")
}
}
}

init() {
self.serverURL = UserDefaults.standard.string(forKey: "serverURL") ?? "http://localhost:10100"
self.model = UserDefaults.standard.string(forKey: "model") ?? "claude-opus-4-6"
self.cwd = UserDefaults.standard.string(forKey: "cwd") ?? ""
self.sessionId = UserDefaults.standard.string(forKey: "sessionId")
self.sessionId = KeychainHelper.load(key: "sessionId")
// Migrate: if sessionId was previously in UserDefaults, move it
// to Keychain and remove the plaintext copy.
if sessionId == nil, let legacy = UserDefaults.standard.string(forKey: "sessionId") {
self.sessionId = legacy
UserDefaults.standard.removeObject(forKey: "sessionId")
}
}

func resetSession() {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kcode",
"version": "2.10.13",
"version": "2.10.39",
"description": "AI-powered coding assistant for the terminal - by Astrolexis",
"author": "Astrolexis",
"module": "src/index.ts",
Expand Down
19 changes: 18 additions & 1 deletion src/bridge/websocket-server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
// KCode Bridge/Daemon Mode - WebSocket Server
// Bun-native WebSocket server for bidirectional daemon communication.

import { timingSafeEqual } from "node:crypto";
import type { Server, ServerWebSocket } from "bun";
import { log } from "../core/logger";

/**
* Constant-time comparison of two auth tokens. Prevents timing
* side-channel attacks that could recover a token character by
* character. Both inputs are normalized to equal-length Buffers
* before comparison, since timingSafeEqual throws on length mismatch.
*/
function timingSafeTokenEqual(supplied: string, expected: string): boolean {
const a = Buffer.from(supplied);
const b = Buffer.from(expected);
if (a.length !== b.length) {
timingSafeEqual(b, b);
return false;
}
return timingSafeEqual(a, b);
}
import type { PermissionBridge } from "./permission-bridge";
import { createMessage, isClientMessageType, parseMessage, serializeMessage } from "./protocol";
import type { SessionManager } from "./session-manager";
Expand Down Expand Up @@ -102,7 +119,7 @@ export class BridgeWebSocketServer {
? authHeader.slice(7)
: (url.searchParams.get("token") ?? "");

if (token !== self.token) {
if (!timingSafeTokenEqual(token, self.token)) {
return new Response("Unauthorized", { status: 401 });
}

Expand Down
Loading
Loading