Mostly safe โ a couple of notes worth reading.
Scanned 5/1/2026, 11:16:54 AMยทCached resultยทDeep Scanยท88 rulesยทHow we decide โ
AIVSS Score
Low
Severity Breakdown
0
critical
5
high
62
medium
0
low
MCP Server Information
Findings
This package receives a B grade with a safety score of 53/100, driven primarily by 62 medium-severity issues and 5 high-severity vulnerabilities across server configuration, ANSI escape injection, and container security concerns. The 44 server configuration findings suggest potential misconfigurations that could expose the service to attacks, while 18 ANSI escape injection vulnerabilities could allow attackers to manipulate terminal output or logs. You should address the high-severity issues and review the server configuration settings before deployment, though the absence of critical vulnerabilities means this isn't an immediate blocker if you can mitigate these risks.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 โ 40 of 67 findings. Click any finding to read.
No known CVEs found for this package or its dependencies.
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnโt shipped yet.
Showing 1โ30 of 67 findings
67 findings
Manifest explicitly declares authentication is OFF (`"auth": "none"` / `false` / `null` / `"authentication": null`). The author considered auth and declined it โ every tool call reaches the server without credentials. Set a real mechanism (bearer, oauth, mtls, apiKey) or run the server stdio-only behind a trusted host boundary.
Evidence
| 639 | "path": "/health", |
| 640 | "methods": ["GET"], |
| 641 | "description": "Health check endpoint", |
| 642 | "auth": "none" |
| 643 | } |
| 644 | ] |
| 645 | } |
RemediationAI
The health check endpoint in ToolsCreate.swift explicitly sets `"auth": "none"`, allowing unauthenticated access to a sensitive endpoint. Change the manifest entry to `"auth": "bearer"` and implement Bearer token validation in the handler using a middleware that extracts and validates the Authorization header before processing the request. This ensures only authenticated clients can reach the health endpoint. Verify by attempting a request without an Authorization headerโit should return 401 Unauthorized.
Manifest explicitly declares authentication is OFF (`"auth": "none"` / `false` / `null` / `"authentication": null`). The author considered auth and declined it โ every tool call reaches the server without credentials. Set a real mechanism (bearer, oauth, mtls, apiKey) or run the server stdio-only behind a trusted host boundary.
Evidence
| 615 | "path": "/callback", |
| 616 | "methods": ["GET"], |
| 617 | "description": "OAuth 2.0 callback handler", |
| 618 | "auth": "none" |
| 619 | }, |
| 620 | { |
| 621 | "id": "webhook", |
RemediationAI
The OAuth callback handler in PLUGIN_AUTHORING.md documentation example declares `"auth": "none"`, which is unsafe for a callback endpoint that processes sensitive OAuth state. Replace `"auth": "none"` with `"auth": "oauth"` and implement CSRF token validation and state parameter verification in the callback handler. This prevents attackers from forging callback requests. Test by sending a callback request without valid state/CSRF tokensโit should reject the request.
Manifest explicitly declares authentication is OFF (`"auth": "none"` / `false` / `null` / `"authentication": null`). The author considered auth and declined it โ every tool call reaches the server without credentials. Set a real mechanism (bearer, oauth, mtls, apiKey) or run the server stdio-only behind a trusted host boundary.
Evidence
| 491 | "path": "/callback", |
| 492 | "methods": ["GET"], |
| 493 | "description": "OAuth callback", |
| 494 | "auth": "none" |
| 495 | }, |
| 496 | { |
| 497 | "id": "webhook", |
RemediationAI
The OAuth callback endpoint in PluginTests.swift test fixture declares `"auth": "none"`, leaving the callback vulnerable in test scenarios that may be copy-pasted into production. Update the test manifest to use `"auth": "oauth"` and mock the OAuth state validation in the test setup. This ensures tests validate authentication logic rather than bypassing it. Run the test and confirm it fails if the mocked OAuth state is invalid.
Manifest explicitly declares authentication is OFF (`"auth": "none"` / `false` / `null` / `"authentication": null`). The author considered auth and declined it โ every tool call reaches the server without credentials. Set a real mechanism (bearer, oauth, mtls, apiKey) or run the server stdio-only behind a trusted host boundary.
Evidence
| 217 | "path": "/callback", |
| 218 | "methods": ["GET"], |
| 219 | "description": "OAuth 2.0 callback handler", |
| 220 | "auth": "none" |
| 221 | }, |
| 222 | { |
| 223 | "id": "webhook", |
RemediationAI
The OAuth callback handler documentation example in PLUGIN_AUTHORING.md repeats the `"auth": "none"` pattern, which could mislead plugin authors to deploy insecure endpoints. Update all documentation examples to show `"auth": "oauth"` with accompanying code snippets demonstrating state parameter and CSRF token validation. This sets the correct security baseline for developers. Verify by reviewing the updated docs to ensure no examples show disabled auth for sensitive endpoints.
Manifest explicitly declares authentication is OFF (`"auth": "none"` / `false` / `null` / `"authentication": null`). The author considered auth and declined it โ every tool call reaches the server without credentials. Set a real mechanism (bearer, oauth, mtls, apiKey) or run the server stdio-only behind a trusted host boundary.
Evidence
| 308 | "path": "/health", |
| 309 | "methods": ["GET"], |
| 310 | "description": "Health check endpoint", |
| 311 | "auth": "none" |
| 312 | } |
| 313 | ] |
| 314 | } |
RemediationAI
The health check endpoint in ToolsCreate.swift again declares `"auth": "none"`, allowing any caller to probe the server. Change `"auth": "none"` to `"auth": "bearer"` and add a Bearer token check in the health endpoint handler. For health checks, consider using a separate internal-only endpoint or requiring a valid API key. Test by calling the endpoint without credentialsโit should return 401.
Container base image uses the floating :latest tag. This provides no reproducibility โ the image content can change under you, and there is no signature to verify.
Evidence
| 1 | FROM alpine:latest |
| 2 | |
| 3 | RUN mkdir -p /run /tmp |
RemediationAI
The Dockerfile uses `FROM alpine:latest`, which pulls an unpinned image that can change unexpectedly and breaks reproducibility. Replace `FROM alpine:latest` with a specific pinned version like `FROM alpine:3.18.4` (check the current stable release). This ensures consistent builds and allows signature verification. Verify by running `docker build` twice and confirming the image digest is identical both times.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1052 | print("[SpeechService] Setting input device to AudioDeviceID: \(deviceId)") |
| 1053 | Self.setInputDevice(deviceId, for: inputNode) |
| 1054 | } else { |
| 1055 | print("[SpeechService] Using system default input device") |
| 1056 | } |
| 1057 | |
| 1058 | let hwFormat = inputNode.inputFormat(forBus: 0) |
RemediationAI
The SpeechService.swift prints the user-controlled `deviceId` variable directly without sanitizing ANSI escape sequences, allowing injection attacks. Replace `print("[SpeechService] Setting input device to AudioDeviceID: \(deviceId)")` with a sanitized version: `print("[SpeechService] Setting input device to AudioDeviceID: \(deviceId.replacingOccurrences(of: "\u{1B}", with: ""))")` or use a logging library with built-in sanitization. This prevents terminal manipulation. Test by passing a deviceId containing escape sequences (e.g., `\u{1B}[2J`) and verify the output is not interpreted as a control sequence.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 462 | voiceInputState = .recording |
| 463 | lastVoiceActivityTime = Date() |
| 464 | resetPauseDetectionForRecording() |
| 465 | print("[FloatingInputCard] Recording confirmed - voice input ready") |
| 466 | } else if voiceInputState == .idle { |
| 467 | print("[FloatingInputCard] External recording detected. Overlay: \(showVoiceOverlay)") |
| 468 | voiceInputState = .recording |
RemediationAI
FloatingInputCard.swift prints a static string without sanitizing any user-controlled data in the surrounding context. While this specific print is safe, audit the function for any user input (e.g., `voiceInputState` values) being interpolated. If user data is printed, sanitize it: `print("[FloatingInputCard] Recording confirmed - voice input ready")` (keep static) or sanitize dynamic values before printing. Verify by checking that all print statements in the function contain only static strings or sanitized variables.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 476 | let vadConfig = VADConfigurationStore.load() |
| 477 | if vadConfig.autoStartVoiceInput { |
| 478 | try? await Task.sleep(nanoseconds: 200_000_000) // 200ms - fast handoff |
| 479 | print("[AppDelegate] Triggering voice input in chat for window \(targetWindowId)") |
| 480 | NotificationCenter.default.post( |
| 481 | name: .startVoiceInputInChat, |
| 482 | object: targetWindowId // Target specific window |
RemediationAI
AppDelegate.swift prints `targetWindowId` without sanitization, which could contain ANSI escape sequences if derived from user input. Replace `print("[AppDelegate] Triggering voice input in chat for window \(targetWindowId)")` with sanitized output: `let safeWindowId = String(targetWindowId).replacingOccurrences(of: "\u{1B}", with: ""); print("[AppDelegate] Triggering voice input in chat for window \(safeWindowId)")`. This prevents terminal injection. Test by passing a windowId with escape sequences and confirm the output is literal text.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 879 | } |
| 880 | |
| 881 | private func transferToTextInput() { |
| 882 | print("[FloatingInputCard] Transferring to text input - disabling continuous mode") |
| 883 | // Transfer transcription to text input and close overlay |
| 884 | let transcribedText = [ |
| 885 | speechService.confirmedTranscription, |
RemediationAI
FloatingInputCard.swift prints a static message but may interpolate user-controlled transcription data elsewhere in the function. Audit the `transferToTextInput()` function for any print statements that include `transcribedText` or other user input. If found, sanitize before printing: `let safeTrans = transcribedText.replacingOccurrences(of: "\u{1B}", with: ""); print("Text: \(safeTrans)")`. Verify by checking all print statements in the function for user-controlled interpolations.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1056 | } |
| 1057 | |
| 1058 | let hwFormat = inputNode.inputFormat(forBus: 0) |
| 1059 | print( |
| 1060 | "[SpeechService] Hardware input format: \(hwFormat.sampleRate)Hz, \(hwFormat.channelCount) channels" |
| 1061 | ) |
| 1062 | |
| 1063 | guard hwFormat.sampleRate > 0, hwFormat.channelCount > 0 else { |
RemediationAI
SpeechService.swift prints hardware format details (sampleRate, channelCount) which are system values, but the pattern is unsafe if these values come from untrusted sources. Ensure `hwFormat.sampleRate` and `hwFormat.channelCount` are always from the audio system API (trusted). If these could be user-influenced, sanitize: `print("[SpeechService] Hardware input format: \(Int(hwFormat.sampleRate))Hz, \(Int(hwFormat.channelCount)) channels")` with explicit type casting. Verify by confirming the values originate from AVAudioFormat system APIs.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1058 | context.write(NIOAny(HTTPServerResponsePart.body(.byteBuffer(buffer))), promise: nil) |
| 1059 | context.flush() |
| 1060 | } catch { |
| 1061 | print("Error encoding Open Responses SSE event: \(error)") |
| 1062 | context.close(promise: nil) |
| 1063 | } |
| 1064 | } |
RemediationAI
ResponseWriters.swift prints the `error` object directly without sanitization, which could contain user-controlled error messages with ANSI codes. Replace `print("Error encoding Open Responses SSE event: \(error)")` with `print("Error encoding Open Responses SSE event: \(error.localizedDescription.replacingOccurrences(of: "\u{1B}", with: ""))")`. This sanitizes error messages before display. Test by triggering an encoding error with a crafted error message containing escape sequences.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 792 | print("[FloatingInputCard] Silence timeout with content - triggering auto-send") |
| 793 | voiceInputState = .paused(remaining: voiceConfig.confirmationDelay) |
| 794 | } else if !hasContent { |
| 795 | print("[FloatingInputCard] Silence timeout without content - closing voice input") |
| 796 | stopVoiceInputFromTimeout() |
| 797 | } |
| 798 | } |
RemediationAI
FloatingInputCard.swift prints a static message without user input, but audit the surrounding context for any user-controlled data. The current print is safe, but ensure no user input (e.g., voice config values) is interpolated in nearby prints. Verify by reviewing all print statements in the silence timeout handler for user-controlled variables.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 690 | // lastVoiceActivityTime is reset in onChange(of: isRecording) |
| 691 | |
| 692 | } catch { |
| 693 | print("[FloatingInputCard] Failed to start voice input: \(error)") |
| 694 | await MainActor.run { |
| 695 | voiceInputState = .idle |
| 696 | showVoiceOverlay = false |
RemediationAI
FloatingInputCard.swift prints the `error` object directly, which may contain user-influenced error text with ANSI sequences. Replace `print("[FloatingInputCard] Failed to start voice input: \(error)")` with `print("[FloatingInputCard] Failed to start voice input: \(error.localizedDescription.replacingOccurrences(of: "\u{1B}", with: ""))")`. This sanitizes error output. Test by triggering a voice input failure with a crafted error message.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1267 | if status != noErr { |
| 1268 | print("[SpeechService] Failed to set input device: \(status). Error code: \(status)") |
| 1269 | } else { |
| 1270 | print("[SpeechService] Successfully set input device to AudioDeviceID: \(deviceId)") |
| 1271 | } |
| 1272 | } |
RemediationAI
SpeechService.swift prints the `status` error code directly, which is a system value and safe, but the pattern should be consistent. Ensure all error code prints use explicit formatting: `print("[SpeechService] Failed to set input device: \(Int(status)). Error code: \(Int(status))")` to prevent any potential injection. Verify by confirming status is always an OSStatus system type.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 132 | print("[ClipboardService] copySelection() call returned: \(posted)") |
| 133 | |
| 134 | if !posted { |
| 135 | print("[ClipboardService] FAILED to post Cmd+C event. Likely missing accessibility permissions.") |
| 136 | return nil |
| 137 | } |
RemediationAI
ClipboardService.swift prints the `posted` boolean and a static message, which is safe. However, audit the function for any user-controlled clipboard data being printed. If clipboard content is logged, sanitize it: `let safeContent = clipboardData.replacingOccurrences(of: "\u{1B}", with: ""); print("[ClipboardService] Content: \(safeContent)")`. Verify by checking all print statements in the function.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1265 | ) |
| 1266 | |
| 1267 | if status != noErr { |
| 1268 | print("[SpeechService] Failed to set input device: \(status). Error code: \(status)") |
| 1269 | } else { |
| 1270 | print("[SpeechService] Successfully set input device to AudioDeviceID: \(deviceId)") |
| 1271 | } |
RemediationAI
SpeechService.swift repeats the pattern of printing `status` error codes, which are system values. Ensure consistency by using explicit type casting: `print("[SpeechService] Failed to set input device: \(Int(status)). Error code: \(Int(status))")`. This prevents any potential injection from error code representation. Verify by confirming status is always from the audio system API.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 700 | } |
| 701 | |
| 702 | private func cancelVoiceInput() { |
| 703 | print("[FloatingInputCard] User cancelled voice input - disabling continuous mode") |
| 704 | hasDetectedSpeechThisTurn = false |
| 705 | lastConfirmedLength = 0 |
| 706 | isContinuousVoiceMode = false |
RemediationAI
FloatingInputCard.swift prints a static message without user input, which is safe. Verify by reviewing the entire `cancelVoiceInput()` function to ensure no user-controlled data (e.g., voice state values) is interpolated in any print statements.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 2775 | // Check if voice input is active AND overlay is visible |
| 2776 | if SpeechService.shared.isRecording && session.showVoiceOverlay { |
| 2777 | // Stage 1: Cancel voice input |
| 2778 | print("[ChatView] Esc pressed: Cancelling voice input") |
| 2779 | Task { |
| 2780 | // Stop streaming and clear transcription |
| 2781 | _ = await SpeechService.shared.stopStreamingTranscription() |
RemediationAI
ChatView.swift prints a static message without user input, which is safe. However, audit the entire escape key handler for any user-controlled data being printed (e.g., session state, window IDs). If found, sanitize before printing. Verify by reviewing all print statements in the escape key handler.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 107 | if installStatus == noErr { |
| 108 | eventHandlerRef = refHandler |
| 109 | } else { |
| 110 | print("[TranscriptionHotKeyManager] Failed to install event handler: \(installStatus)") |
| 111 | } |
| 112 | } |
| 113 | } |
RemediationAI
TranscriptionHotKeyManager.swift prints the `installStatus` error code, which is a system value and safe. Ensure consistency by using explicit type casting: `print("[TranscriptionHotKeyManager] Failed to install event handler: \(Int(installStatus))")`. Verify by confirming installStatus is always from the system API.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 129 | return false |
| 130 | } |
| 131 | |
| 132 | print("[KeyboardSimulationService] Posting Cmd+C event...") |
| 133 | Self.typeKeyCode(UInt16(kVK_ANSI_C), modifiers: .maskCommand) |
| 134 | return true |
| 135 | } |
RemediationAI
KeyboardSimulationService.swift prints a static message without user input, which is safe. Verify by reviewing the entire function to ensure no user-controlled data is interpolated in print statements.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 1049 | let inputNode = engine.inputNode |
| 1050 | |
| 1051 | if let deviceId = targetDeviceId { |
| 1052 | print("[SpeechService] Setting input device to AudioDeviceID: \(deviceId)") |
| 1053 | Self.setInputDevice(deviceId, for: inputNode) |
| 1054 | } else { |
| 1055 | print("[SpeechService] Using system default input device") |
RemediationAI
SpeechService.swift prints `deviceId` without sanitization, allowing ANSI injection if deviceId is user-influenced. Replace `print("[SpeechService] Setting input device to AudioDeviceID: \(deviceId)")` with `print("[SpeechService] Setting input device to AudioDeviceID: \(String(deviceId).replacingOccurrences(of: "\u{1B}", with: ""))")`. This sanitizes the device ID. Test by passing a deviceId with escape sequences.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 215 | await context.start(prompt: request.prompt) |
| 216 | |
| 217 | let reattachNote = reattach == nil ? "" : " (reattached to session \(context.id))" |
| 218 | print("[BackgroundTaskManager] Dispatched chat task: \(request.title ?? "untitled")\(reattachNote)") |
| 219 | // Return the resolved task id (may differ from request.id after a |
| 220 | // reattach) so callers awaiting completion poll the actual live task. |
| 221 | return DispatchHandle(id: context.id, request: request) |
RemediationAI
BackgroundTaskManager.swift prints `request.title` without sanitization, which is user-controlled and could contain ANSI sequences. Replace `print("[BackgroundTaskManager] Dispatched chat task: \(request.title ?? "untitled")\(reattachNote)")` with `let safeTitle = (request.title ?? "untitled").replacingOccurrences(of: "\u{1B}", with: ""); print("[BackgroundTaskManager] Dispatched chat task: \(safeTitle)\(reattachNote)")`. This sanitizes the task title. Test by passing a title with escape sequences.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 662 | context.write(NIOAny(HTTPServerResponsePart.body(.byteBuffer(buffer))), promise: nil) |
| 663 | context.flush() |
| 664 | } catch { |
| 665 | print("Error encoding Anthropic SSE event: \(error)") |
| 666 | context.close(promise: nil) |
| 667 | } |
| 668 | } |
RemediationAI
ResponseWriters.swift repeats the pattern of printing error objects without sanitization. Replace `print("Error encoding Anthropic SSE event: \(error)")` with `print("Error encoding Anthropic SSE event: \(error.localizedDescription.replacingOccurrences(of: "\u{1B}", with: ""))")`. This sanitizes error messages. Test by triggering an encoding error with a crafted message.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 289 | } |
| 290 | console.log("Osaurus at", inst.url, "LAN:", !!inst.exposeToNetwork); |
| 291 | // Example request (Node 18+ has global fetch in Electron; otherwise use axios/node-fetch) |
| 292 | const resp = await fetch(new URL("/v1/models", inst.url)); |
| 293 | const models = await resp.json(); |
| 294 | console.log(models); |
| 295 | } |
RemediationAI
The fetch call in SHARED_CONFIGURATION_GUIDE.md documentation lacks a timeout, allowing a hung server to block indefinitely. Replace `const resp = await fetch(new URL("/v1/models", inst.url));` with `const resp = await fetch(new URL("/v1/models", inst.url), { signal: AbortSignal.timeout(5000) });` (requires Node 17+) or use a manual timeout wrapper. This enforces a 5-second timeout. Verify by testing with a server that never respondsโthe request should abort after 5 seconds.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 919 | The frontend can use these values for API calls: |
| 920 | |
| 921 | ```javascript |
| 922 | const res = await fetch(`${window.__osaurus.baseUrl}/api/widgets`); |
| 923 | ``` |
| 924 | |
| 925 | ### Plugin Skills (SKILL.md) |
RemediationAI
The fetch call in PLUGIN_AUTHORING.md documentation lacks a timeout. Replace `const res = await fetch(\`${window.__osaurus.baseUrl}/api/widgets\`);` with `const res = await fetch(\`${window.__osaurus.baseUrl}/api/widgets\`, { signal: AbortSignal.timeout(5000) });`. This adds a 5-second timeout. Test by calling a non-responsive endpoint and confirming the request times out.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 139 | // MARK: - Submission |
| 140 | |
| 141 | /// Tokenize the chat + tools, fetch (or create) the per-model |
| 142 | /// `BatchEngine`, and submit one request via `engine.generate`. Returns |
| 143 | /// the resulting `Generation` stream wrapped with cancellation plumbing. |
| 144 | static func generate( |
RemediationAI
The MLXBatchAdapter.swift generate function lacks explicit timeout documentation for the `engine.generate()` call, which could hang indefinitely. Add a timeout wrapper: `let timeoutTask = Task { try await Task.sleep(nanoseconds: 30_000_000_000); throw TimeoutError() }; let result = try await withThrowingTaskGroup(...) { ... }` or use a library like `async-timeout`. This enforces a 30-second timeout. Verify by testing with a model that hangsโthe request should timeout.
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 348 | "description": "Execute AppleScript commands", |
| 349 | "parameters": { |
| 350 | "type": "object", |
| 351 | "properties": { "script": { "type": "string" } } |
| 352 | }, |
| 353 | "requirements": ["automation"], |
| 354 | "permission_policy": "ask" |
RemediationAI
The AppleScript tool in PLUGIN_AUTHORING.md documentation accepts an unconstrained `script` string parameter, allowing arbitrary code execution. Add schema constraints: `"script": { "type": "string", "maxLength": 10000, "pattern": "^[a-zA-Z0-9\\s\\-\\(\\)\\"\\':.,]*$" }` to restrict to safe characters, or use an enum of pre-approved scripts. This limits the blast radius. Test by attempting to pass a script with shell metacharactersโit should be rejected.
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 1720 | { |
| 1721 | "type": "object", |
| 1722 | "properties": { |
| 1723 | "query": { |
| 1724 | "type": "string", |
| 1725 | "description": "Search query text" |
| 1726 | }, |
| 1727 | "limit": { |
| 1728 | "type": "integer", |
| 1729 | "description": "Maximum results to return", |
RemediationAI
The search tool in ToolsCreate.swift accepts an unconstrained `query` string parameter. Add schema constraints: `"query": { "type": "string", "maxLength": 500, "pattern": "^[a-zA-Z0-9\\s\\-]*$" }` to restrict length and character set. This prevents injection attacks and resource exhaustion. Test by passing a very long query or special charactersโit should be rejected.
Dockerfile never sets a non-root `USER` directive, so the CMD runs as root by default. Any RCE or library-level vulnerability exploited inside this container gets full privileges (MCP Top-10 R3). Add `USER <non-root>` before CMD / ENTRYPOINT in the final stage โ e.g. `USER 1000`, `USER nobody`, or `USER nonroot` on distroless.
Evidence
| 1 | FROM alpine:latest |
| 2 | |
| 3 | RUN mkdir -p /run /tmp |
| 4 | |
| 5 | RUN apk add --no-cache \ |
| 6 | bash \ |
| 7 | python3 python3-dev py3-pip \ |
| 8 | nodejs npm \ |
| 9 | build-base cmake \ |
| 10 | git patch \ |
| 11 | curl wget openssh-client ca-certificates \ |
| 12 | jq tar gzip zip unzip findutils coreutils grep sed less vim \ |
| 13 | file tree ripgrep \ |
| 14 | sqlite sqlite-dev \ |
| 15 | libstdc++ libffi-dev openssl-dev \ |
| 16 | tzdata |
RemediationAI
The sandbox/Dockerfile runs as root by default, giving any RCE full privileges. Add `USER 1000` before the final CMD/ENTRYPOINT: `RUN addgroup -g 1000 sandbox && adduser -D -u 1000 -G sandbox sandbox` followed by `USER 1000`. This runs the container as a non-root user. Verify by running `docker run <image> id`โit should show uid=1000 instead of uid=0.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # API Endpoints Guide |
| 2 | |
| 3 | This guide explains how to use the API endpoints in Osaurus, including OpenAI-compatible, Anthropic-compatible, and Open Responses formats. |
| 4 | |
| 5 | ## Available Endpoints |
| 6 | |
| 7 | ### 1. List Models - `GET /models` (also available at `GET /v1/models`) |
| 8 | |
| 9 | Returns a list of available models that are currently downloaded and ready to use. |
| 10 | |
| 11 | ```bash |
| 12 | curl http://127.0.0.1:1337/models |
| 13 | ``` |
| 14 | |
| 15 | Example response: |
| 16 | |
| 17 | ```json |
| 18 | { |
| 19 | "object": "list", |
| 20 | "data": [ |
| 21 | { |
| 22 | "id": "llama-3.2-3b-instruct", |
| 23 | |
RemediationAI
The OpenAI_API_GUIDE.md documentation describes API endpoints but does not declare an authentication mechanism. Add explicit auth documentation: "All endpoints require Bearer token authentication via the `Authorization: Bearer <token>` header. Tokens are issued via the `/auth/token` endpoint." This clarifies the security model. Verify by reviewing the updated docs to ensure all endpoints list their auth requirements.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # Sandbox |
| 2 | |
| 3 | Run agent code in an isolated Linux virtual machine โ safely, locally, and with full dev environment capabilities. |
| 4 | |
| 5 | The Sandbox is a shared Linux container powered by Apple's [Containerization](https://developer.apple.com/documentation/containerization) framework. It gives every Osaurus agent access to a real Linux environment with shell, package managers, compilers, and file system access โ all running natively on Apple Silicon with zero risk to your Mac. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## Why Sandbox? |
| 10 | |
| 11 | ### S |
RemediationAI
The SANDBOX.md documentation describes the sandbox feature but does not declare authentication. Add explicit auth documentation: "The sandbox container is isolated and accessed only by authenticated Osaurus instances. Communication uses mTLS with certificate pinning." This clarifies the security boundary. Verify by reviewing the updated docs.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "id": "preflight.music.spotify-play", |
| 3 | "domain": "preflight", |
| 4 | "label": "music โข spotify pick (multi-plugin smoke)", |
| 5 | "query": "play some lo-fi beats on spotify", |
| 6 | "fixtures": { |
| 7 | "preflightMode": "balanced" |
| 8 | }, |
| 9 | "expect": { |
| 10 | "tools": { |
| 11 | "mustNotInclude": ["browser_navigate"] |
| 12 | } |
| 13 | } |
| 14 | } |
RemediationAI
The music-spotify.json eval fixture does not declare authentication for the tools it tests. Add an `"auth"` field to the fixture metadata: `"auth": "oauth"` to indicate that Spotify tools require OAuth. This clarifies the security model for test reviewers. Verify by checking that the fixture documentation lists auth requirements.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # Developer Tools |
| 2 | |
| 3 | Osaurus includes built-in developer tools for debugging, monitoring, and testing your integration. Access them via the Management window (`โ Shift M`). |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## Insights |
| 8 | |
| 9 | The **Insights** tab provides real-time monitoring of all API requests flowing through Osaurus. |
| 10 | |
| 11 | ### Accessing Insights |
| 12 | |
| 13 | 1. Open the Management window (`โ Shift M`) |
| 14 | 2. Click **Insights** in the sidebar |
| 15 | |
| 16 | ### Features |
| 17 | |
| 18 | #### Request Logging |
| 19 | |
| 20 | Every API request is logged with: |
| 21 | |
| 22 | | Field | Description |
RemediationAI
The DEVELOPER_TOOLS.md documentation describes the Insights tab and API access but does not declare authentication. Add explicit auth documentation: "The Insights API requires Bearer token authentication. All requests must include the `Authorization: Bearer <token>` header." This clarifies the security model. Verify by reviewing the updated docs.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "id": "preflight.no-match.gibberish", |
| 3 | "domain": "preflight", |
| 4 | "label": "gibberish โข abstain on nonsense", |
| 5 | "query": "zzz_completely_nonexistent_capability_xyz_12345 ablubblub", |
| 6 | "fixtures": { |
| 7 | "preflightMode": "balanced" |
| 8 | }, |
| 9 | "expect": { |
| 10 | "tools": { |
| 11 | "mustNotInclude": [ |
| 12 | "browser_navigate", |
| 13 | "sandbox_exec", |
| 14 | "render_chart" |
| 15 | ] |
| 16 | } |
| 17 | } |
| 18 | } |
RemediationAI
The no-match-abstain.json eval fixture does not declare authentication. Add an `"auth"` field to the fixture metadata or add a note in the fixture documentation clarifying that this is a test fixture and authentication is not required for test scenarios. This prevents confusion about production security. Verify by reviewing the fixture documentation.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "id": "preflight.sandbox.plugin-creator-noise-floor", |
| 3 | "domain": "preflight", |
| 4 | "label": "sandbox โข picker doesn't pick non-existent plugin tools", |
| 5 | "query": "I need a tool to call the AcmeWidgets API", |
| 6 | "fixtures": { |
| 7 | "preflightMode": "balanced" |
| 8 | }, |
| 9 | "expect": { |
| 10 | "tools": { |
| 11 | "mustNotInclude": [ |
| 12 | "acmewidgets_api", |
| 13 | "acmewidgets_call" |
| 14 | ] |
| 15 | } |
| 16 | } |
| 17 | } |
RemediationAI
The sandbox-plugin-creator.json eval fixture does not declare authentication. Add an `"auth"` field to the fixture metadata: `"auth": "bearer"` to indicate that plugin creation requires authentication. This clarifies the security model. Verify by checking the fixture documentation.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # OsaurusEvals |
| 2 | |
| 3 | Catalog-driven behaviour / integration tests for Osaurus that hit a real model (Foundation, MLX, remote provider). |
| 4 | |
| 5 | These evals are deliberately **off the CI path**. They burn LLM tokens, depend on local plugin installs, and exist to help us tune capabilities and triage new models โ not to gate every commit. |
| 6 | |
| 7 | ## Structure |
| 8 | |
| 9 | ``` |
| 10 | Packages/OsaurusEvals/ |
| 11 | Package.swift |
| 12 | README.md (this file) |
| 13 | Sources/ |
| 14 | OsaurusEvalsKit/ โ library (case schema, runner, scorers, model override) |
| 15 |
RemediationAI
The OsaurusEvals README.md does not declare authentication for the eval framework. Add explicit documentation: "Evals run against real models and require authentication via API keys or OAuth tokens. See AUTHENTICATION.md for setup." This clarifies the security model. Verify by reviewing the updated README.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | // |
| 2 | // Skill.swift |
| 3 | // osaurus |
| 4 | // |
| 5 | // Defines a Skill - markdown instructions that guide AI behavior. |
| 6 | // Skills are stored as directories with SKILL.md files following the Agent Skills spec. |
| 7 | // See: https://agentskills.io/specification |
| 8 | // |
| 9 | |
| 10 | import Foundation |
| 11 | |
| 12 | /// Represents a file within a skill's references or assets directory |
| 13 | public struct SkillFile: Codable, Identifiable, Sendable, Equatable { |
| 14 | public var id: String { name } |
| 15 | public let name: String |
| 16 | public let relativePath: String |
| 17 |
RemediationAI
The Skill.swift model file does not declare authentication for skills. Add documentation in the file: `/// Skills are accessed via authenticated API endpoints. See SKILL_AUTH.md for details.` This clarifies the security model. Verify by reviewing the updated file documentation.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "id": "preflight.browser.amazon-orders", |
| 3 | "domain": "preflight", |
| 4 | "label": "browser โข amazon orders", |
| 5 | "query": "can you help me check my orders on amazon?", |
| 6 | "fixtures": { |
| 7 | "preflightMode": "balanced", |
| 8 | "requirePlugins": ["osaurus.browser"] |
| 9 | }, |
| 10 | "expect": { |
| 11 | "tools": { |
| 12 | "mustInclude": ["browser_navigate"] |
| 13 | }, |
| 14 | "companions": { |
| 15 | "skills": ["Osaurus Browser"], |
| 16 | "siblings": { |
| 17 | "minOverlap": 2, |
| 18 | "candidates": [ |
| 19 | "browser_open_login", |
| 20 | |
RemediationAI
The browser-amazon-orders.json eval fixture does not declare authentication. Add an `"auth"` field to the fixture metadata: `"auth": "oauth"` to indicate that Amazon tools require OAuth. This clarifies the security model. Verify by checking the fixture documentation.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "id": "preflight.browser.login-flow", |
| 3 | "domain": "preflight", |
| 4 | "label": "browser โข login picks open_login directly", |
| 5 | "query": "can you sign me in to my github account?", |
| 6 | "fixtures": { |
| 7 | "preflightMode": "balanced", |
| 8 | "requirePlugins": ["osaurus.browser"] |
| 9 | }, |
| 10 | "expect": { |
| 11 | "tools": { |
| 12 | "mustInclude": ["browser_open_login"] |
| 13 | }, |
| 14 | "companions": { |
| 15 | "skills": ["Osaurus Browser"] |
| 16 | } |
| 17 | } |
| 18 | } |
RemediationAI
The browser-login-flow.json eval fixture does not declare authentication. Add an `"auth"` field to the fixture metadata: `"auth": "oauth"` to indicate that login flows require OAuth. This clarifies the security model. Verify by checking the fixture documentation.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | <p align="center"> |
| 2 | <img width="865" height="677" alt="Screenshot 2026-03-19 at 3 42 04โฏPM" src="https://github.com/user-attachments/assets/c16ee8bb-7f31-4659-9c2c-6eaaf8441c26" /> |
| 3 | </p> |
| 4 | |
| 5 | <h1 align="center">Osaurus</h1> |
| 6 | |
| 7 | <p align="center"> |
| 8 | <strong>Own your AI.</strong><br> |
| 9 | Agents, memory, tools, and identity that live on your Mac. Built purely in Swift. Fully offline. Open source. |
| 10 | </p> |
| 11 | |
| 12 | <p align="center"> |
| 13 | <a href="https://github.com/osaurus-ai/osaurus/releases/latest"><img src="https://img.s |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "id": "preflight.smalltalk.thanks", |
| 3 | "domain": "preflight", |
| 4 | "label": "smalltalk โข abstain (NONE) regression", |
| 5 | "query": "thanks, that's perfect", |
| 6 | "fixtures": { |
| 7 | "preflightMode": "balanced" |
| 8 | }, |
| 9 | "expect": { |
| 10 | "tools": { |
| 11 | "mustNotInclude": [ |
| 12 | "browser_navigate", |
| 13 | "sandbox_exec" |
| 14 | ] |
| 15 | } |
| 16 | } |
| 17 | } |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | // |
| 2 | // AnthropicAPITests.swift |
| 3 | // osaurusTests |
| 4 | // |
| 5 | // Tests for Anthropic Messages API compatibility. |
| 6 | // |
| 7 | |
| 8 | import Foundation |
| 9 | import Testing |
| 10 | |
| 11 | @testable import OsaurusCore |
| 12 | |
| 13 | struct AnthropicAPITests { |
| 14 | |
| 15 | // MARK: - Request Parsing Tests |
| 16 | |
| 17 | @Test func parseSimpleAnthropicRequest() throws { |
| 18 | let json = """ |
| 19 | { |
| 20 | "model": "claude-3-5-sonnet-20241022", |
| 21 | "max_tokens": 1024, |
| 22 | "messages": [ |
| 23 | {"role": "user", "content": "He |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | {"model":"foundation","messages":[{"role":"user","content":"Using tools if available, get weather for city=San Francisco."}],"tools":[{"type":"function","function":{"name":"get_weather","description":"Get weather by city","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}],"tool_choice":"auto","stream":true,"temperature":0,"max_tokens":64} |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
File registers a state-changing HTTP route (POST / PUT / PATCH / DELETE) but no CSRF protection middleware is applied anywhere in the file. If the server uses cookie-based session auth, a cross-site request from any origin can hit this route while the user's cookies ride along. Apply CSRF middleware: - Express: `csurf` / `csrf-csrf` / `lusca.csrf()` - FastAPI: `fastapi-csrf-protect` - Flask: `flask_wtf.csrf.CSRFProtect` Or, if the route is a JSON API authenticated by bearer tokens (no co
Evidence
| 1 | import Foundation |
| 2 | import Testing |
| 3 | |
| 4 | @testable import OsaurusCore |
| 5 | |
| 6 | /// These tests boot an Apple Containerization Linux VM and run real |
| 7 | /// `pip install`, `npm`, and `go test` workloads inside the sandbox. They |
| 8 | /// take minutes and require Containerization tooling, so they are gated by |
| 9 | /// `OSAURUS_RUN_SANDBOX_INTEGRATION_TESTS=1` and reported as `Disabled` |
| 10 | /// in CI rather than silently passing. |
| 11 | private let isSandboxIntegrationEnabled = |
| 12 | ProcessInfo.processInfo.environment["OSAURUS_RUN_SANDBOX_ |
Remediation
Apply CSRF middleware at the route or router level: - Express: `app.use(csurf())` / `csrf-csrf` package - FastAPI: `fastapi-csrf-protect` with `Depends(...)` - Flask: `CSRFProtect(app)` from `flask_wtf.csrf` Or move to bearer-token auth and set `SameSite=Strict` / `SameSite=Lax` on any session cookies. Document the choice in the project README so reviewers can confirm intent.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 370 | timeout-minutes: 10 |
| 371 | steps: |
| 372 | - name: Checkout code |
| 373 | uses: actions/checkout@v5 |
| 374 | |
| 375 | - name: Install shellcheck |
| 376 | run: sudo apt-get update && sudo apt-get install -y shellcheck |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 17 | steps: |
| 18 | - uses: actions/checkout@v4 |
| 19 | |
| 20 | - uses: docker/setup-qemu-action@v3 |
| 21 | |
| 22 | - uses: docker/setup-buildx-action@v3 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 65 | uses: actions/checkout@v5 |
| 66 | |
| 67 | - name: Set up Xcode ${{ env.XCODE_VERSION }} |
| 68 | uses: maxim-lobanov/setup-xcode@v1 |
| 69 | with: |
| 70 | xcode-version: ${{ env.XCODE_VERSION }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 19 | pull-requests: write |
| 20 | runs-on: ubuntu-latest |
| 21 | steps: |
| 22 | - uses: release-drafter/release-drafter@v6 |
| 23 | env: |
| 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 86 | # Always restore so `cache-primary-key` is populated for the save |
| 87 | # step at the bottom (the wipe step below handles forced cold |
| 88 | # builds without preventing main from repopulating the cache). |
| 89 | uses: actions/cache/restore@v5 |
| 90 | with: |
| 91 | path: ~/Library/Developer/Xcode/DerivedData |
| 92 | # Include vendored C sources (currently the SQLCipher amalgamation |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 319 | # same key. To intentionally invalidate every cache, bump CACHE_SALT. |
| 320 | - name: Save DerivedData cache |
| 321 | if: ${{ github.ref == 'refs/heads/main' && success() && steps.dd-cache.outputs.cache-primary-key != '' }} |
| 322 | uses: actions/cache/save@v5 |
| 323 | with: |
| 324 | path: ~/Library/Developer/Xcode/DerivedData |
| 325 | key: ${{ steps.dd-cache.outputs.cache-primary-key }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 27 | steps: |
| 28 | - name: Checkout code |
| 29 | uses: actions/checkout@v4 |
| 30 | with: |
| 31 | fetch-depth: 0 # Fetch all history for changelog generation |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 107 | run: bash scripts/release/generate_and_deploy_appcast.sh |
| 108 | |
| 109 | - name: Upload artifacts |
| 110 | uses: actions/upload-artifact@v4 |
| 111 | with: |
| 112 | name: osaurus-release-${{ env.VERSION }} |
| 113 | path: | |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 15 | runs-on: ubuntu-latest |
| 16 | |
| 17 | steps: |
| 18 | - uses: actions/checkout@v4 |
| 19 | |
| 20 | - uses: docker/setup-qemu-action@v3 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 62 | XCRESULT_PATH: build/Tests.xcresult |
| 63 | steps: |
| 64 | - name: Checkout code |
| 65 | uses: actions/checkout@v5 |
| 66 | |
| 67 | - name: Set up Xcode ${{ env.XCODE_VERSION }} |
| 68 | uses: maxim-lobanov/setup-xcode@v1 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 134 | steps: |
| 135 | - name: Checkout code |
| 136 | uses: actions/checkout@v4 |
| 137 | with: |
| 138 | token: ${{ secrets.GITHUB_TOKEN }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 305 | # step above: on a wall-timeout the xcresult bundle may be |
| 306 | # partially populated and is still useful for postmortem. |
| 307 | if: ${{ failure() || cancelled() }} |
| 308 | uses: actions/upload-artifact@v5 |
| 309 | with: |
| 310 | name: test-core-xcresult-${{ github.run_attempt }} |
| 311 | path: ${{ env.XCRESULT_PATH }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 21 | - uses: docker/setup-buildx-action@v3 |
| 22 | |
| 23 | - uses: docker/login-action@v3 |
| 24 | with: |
| 25 | registry: ghcr.io |
| 26 | username: ${{ github.actor }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 330 | timeout-minutes: 10 |
| 331 | steps: |
| 332 | - name: Checkout code |
| 333 | uses: actions/checkout@v5 |
| 334 | |
| 335 | # OsaurusCLI's Package.swift requires `swift-tools-version: 6.2`, which |
| 336 | # ships with the pinned Xcode. The runner's default `swift` may be older |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 19 | token: ${{ secrets.GITHUB_TOKEN }} |
| 20 | |
| 21 | - name: Label merged PR |
| 22 | uses: actions/github-script@v7 |
| 23 | with: |
| 24 | script: | |
| 25 | const {owner, repo} = context.repo; |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 32 | fetch-depth: 0 # Fetch all history for changelog generation |
| 33 | |
| 34 | - name: Set up Xcode |
| 35 | uses: maxim-lobanov/setup-xcode@v1 |
| 36 | with: |
| 37 | # Pin to a specific Xcode (was `latest-stable`) so the same |
| 38 | # commit always builds against the same toolchain. Bump |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 357 | timeout-minutes: 10 |
| 358 | steps: |
| 359 | - name: Checkout code |
| 360 | uses: actions/checkout@v5 |
| 361 | |
| 362 | - name: Install SwiftLint |
| 363 | run: brew install swiftlint |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 14 | runs-on: ubuntu-latest |
| 15 | steps: |
| 16 | - name: Checkout repo |
| 17 | uses: actions/checkout@v4 |
| 18 | with: |
| 19 | token: ${{ secrets.GITHUB_TOKEN }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 27 | username: ${{ github.actor }} |
| 28 | password: ${{ secrets.GITHUB_TOKEN }} |
| 29 | |
| 30 | - uses: docker/build-push-action@v6 |
| 31 | with: |
| 32 | context: sandbox |
| 33 | platforms: linux/arm64 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 73 | run: brew install xcbeautify |
| 74 | |
| 75 | - name: Cache SPM packages |
| 76 | uses: actions/cache@v5 |
| 77 | with: |
| 78 | path: ${{ env.SPM_CACHE }} |
| 79 | key: spm-${{ runner.os }}-${{ env.CACHE_SALT }}-xcode${{ env.XCODE_VERSION }}-${{ hashFiles('osaurus.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 19 | - uses: docker/setup-qemu-action@v3 |
| 20 | |
| 21 | - uses: docker/setup-buildx-action@v3 |
| 22 | |
| 23 | - uses: docker/login-action@v3 |
| 24 | with: |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
Identifier whose name suggests PII (email, ssn, phone, dob, credit_card, address) is passed directly to a logging / console / print call. Logs end up in CloudWatch / Datadog / Splunk indexes accessible to a wider audience than the live data โ every PII value leaked into logs becomes a separate compliance liability. Mask before logging: Python: `logger.info("login from %s", redact(email))` Node: `console.log("login", maskEmail(email))` Or move the value to a structured field that the log sh
Evidence
| 598 | func body(content: Content) -> some View { |
| 599 | content |
| 600 | .onChange(of: speechService.microphonePermissionGranted) { _, granted in |
| 601 | print("[VoiceDebug] microphonePermissionGranted โ \(granted)") |
| 602 | voiceDebugLog( |
| 603 | trigger: "micPermission", |
| 604 | enabled: SpeechConfigurationStore.load().voiceInputEnabled, |
Remediation
Mask the value before logging or move it to a structured field the log shipper strips. Per-language idioms: Python: `logger.info("login %s", redact(email))` Node: `console.log("login", maskEmail(email))` For unavoidable diagnostic logging, use a per-tenant pseudonym โ `tokenize(email)` returns a stable hash; logs still group per-user without leaking the underlying address.
get_weather
sandbox_exec