namespace LlamaApp.Common; /// /// Policy for the free-form "custom serve arguments" setting. llama.cpp honors /// the last occurrence of a repeated flag, and the custom tokens are appended /// after the app's own — so without this an entry like --host 0.0.0.0 /// would silently override the validated listen address (and the Settings UI /// would keep claiming "Localhost"). The app owns the flags that define the /// bind, the auth key, and the per-model preset, so those are reserved. /// /// Also provides redaction for the launch log: some llama.cpp flags take /// a secret value, and the raw custom text is otherwise echoed verbatim into /// %LOCALAPPDATA%\Llama\logs. /// public static class ServeArgumentPolicy { /// /// Flags the app sets itself and refuses in custom arguments. --host /// and --port define the reachable endpoint; --api-key the /// app-managed auth; --models-preset the per-model context file; /// --models-dir the "Open With" local-models folder; the /// --ssl* flags change the transport (and would break the /// loopback client's assumptions). /// /// Also the secret-bearing flags: custom arguments become process /// argv, where a value leaks into command-line auditing (Event Log 4688), /// EDR telemetry, crash dumps and process listings. The app's own /// channels exist precisely to keep secrets off argv — the HF token /// travels as the server's HF_TOKEN environment variable, the API key as /// --api-key-file, the key password as --ssl-key-pass's /// file-based counterpart — so a custom-args copy of any of them is /// refused (matching ). /// public static readonly IReadOnlyList ReservedFlags = new[] { "--host", "--port", "--api-key", "--api-key-file", "--models-preset", "--models-dir", "--ssl", "--ssl-key-file", "--ssl-cert-file", "--hf-token", "--ssl-key-pass", }; /// Flags whose value must never be written to the log. public static readonly IReadOnlyList SecretFlags = new[] { "--api-key", "--api-key-file", "--hf-token", "--ssl-key-file", "--ssl-key-pass", }; /// /// Returns a user-facing error when contains a /// reserved flag, or null when the arguments are acceptable. /// public static string? Validate(IReadOnlyList tokens) => FindReservedFlag(tokens) is { } flag ? $"{flag} is managed by Llama and can't be used in custom arguments." : null; /// The first reserved flag present, matched as --flag or --flag=value (case-insensitive). public static string? FindReservedFlag(IReadOnlyList tokens) { foreach (var token in tokens) foreach (var flag in ReservedFlags) if (IsFlag(token, flag)) return flag; return null; } /// /// Renders tokens for logging with secret-flag values replaced by /// *** — both the --flag value and --flag=value forms. /// public static string RedactForLog(IEnumerable tokens) { var output = new List(); var redactNext = false; foreach (var token in tokens) { if (redactNext) { output.Add("***"); redactNext = false; continue; } var secret = SecretFlags.FirstOrDefault(f => IsFlag(token, f)); if (secret is null) { output.Add(token); continue; } // "--flag=value" already carries the value; "--flag value" takes // the next token. if (token.Length > secret.Length && token[secret.Length] == '=') output.Add($"{secret}=***"); else { output.Add(token); redactNext = true; } } return string.Join(' ', output); } private static bool IsFlag(string token, string flag) => token.Equals(flag, StringComparison.OrdinalIgnoreCase) || (token.Length > flag.Length && token.StartsWith(flag, StringComparison.OrdinalIgnoreCase) && token[flag.Length] == '='); }