Fix code scanning findings

This commit is contained in:
rcourtman
2026-03-28 10:58:57 +00:00
parent fda03c531b
commit a8ee51fb99
16 changed files with 53 additions and 29 deletions
+3
View File
@@ -9,6 +9,9 @@ on:
- main
workflow_dispatch:
permissions:
contents: read
jobs:
secret-scan:
name: Secret Scan
+3
View File
@@ -18,6 +18,9 @@ on:
type: boolean
default: false
permissions:
contents: read
concurrency:
group: release-${{ github.event.inputs.version || github.ref || github.run_id }}
cancel-in-progress: false
+3
View File
@@ -5,6 +5,9 @@ on:
# schedule:
# - cron: '0 0 * * *' # Nightly at midnight
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
+3
View File
@@ -19,6 +19,9 @@ on:
description: Pulse API base URL (e.g. http://127.0.0.1:7655)
required: true
permissions:
contents: read
jobs:
eval:
name: Model Matrix Eval
+3
View File
@@ -16,6 +16,9 @@ on:
- "README.md"
workflow_dispatch: {}
permissions:
contents: read
jobs:
lint:
name: Lint and Render Chart
+5
View File
@@ -22,6 +22,9 @@ on:
- '.github/workflows/test-e2e.yml'
workflow_dispatch:
permissions:
contents: read
jobs:
@@ -29,6 +32,8 @@ jobs:
name: Playwright Core E2E
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
# E2E tests are smoke tests - they run but don't block merges
# This reduces friction from flaky tests while maintaining visibility
continue-on-error: true
+6
View File
@@ -23,6 +23,9 @@ on:
- 'tests/integration/**'
workflow_dispatch: # Allow manual triggering
permissions:
contents: read
jobs:
@@ -30,6 +33,9 @@ jobs:
name: Update Flow Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
issues: write
steps:
- name: Checkout code
+3
View File
@@ -10,6 +10,9 @@ on:
required: true
type: string
permissions:
contents: read
jobs:
update-demo:
# Only run for stable releases (not pre-releases) or manual dispatch
@@ -125,7 +125,7 @@ export function ThresholdSlider(props: ThresholdSliderProps) {
<div class="relative">
<div class="w-9 h-4 bg-white dark:bg-gray-800 rounded-full shadow-md border-2 border-current flex items-center justify-center">
<span class="text-[9px] font-semibold">
{props.type === 'temperature' ? `${props.value}${getTemperatureSymbol().replace('°', '°')}` : `${props.value}%`}
{props.type === 'temperature' ? `${props.value}${getTemperatureSymbol()}` : `${props.value}%`}
</span>
</div>
</div>
@@ -43,14 +43,13 @@ export const SetupWizard: Component<SetupWizardProps> = (props) => {
const raw = sessionStorage.getItem(STORAGE_KEYS.SETUP_CREDENTIALS);
if (!raw) return null;
const parsed = JSON.parse(raw) as Partial<WizardState>;
if (!parsed.username || !parsed.password || !parsed.apiToken) {
if (!parsed.username || !parsed.apiToken) {
sessionStorage.removeItem(STORAGE_KEYS.SETUP_CREDENTIALS);
return null;
}
return {
...defaultWizardState,
username: parsed.username,
password: parsed.password,
apiToken: parsed.apiToken,
};
} catch (_err) {
@@ -143,6 +143,9 @@ export const CompleteStep: Component<CompleteStepProps> = (props) => {
const handleCopy = async (type: 'password' | 'token' | 'install', value?: string) => {
const copyValue = value || (type === 'password' ? props.state.password : props.state.apiToken);
if (!copyValue) {
return;
}
const success = await copyToClipboard(copyValue);
if (success) {
setCopied(type);
@@ -165,6 +168,9 @@ export const CompleteStep: Component<CompleteStepProps> = (props) => {
const downloadCredentials = () => {
const baseUrl = getPulseBaseUrl();
const passwordSection = props.state.password
? `Password: ${props.state.password}\n`
: 'Password: not stored after reload; use the password you chose during setup or reset it in Settings.\n';
const content = `Pulse Credentials
==================
Generated: ${new Date().toISOString()}
@@ -173,7 +179,7 @@ Web Login:
----------
URL: ${baseUrl}
Username: ${props.state.username}
Password: ${props.state.password}
${passwordSection}
API Token:
----------
@@ -83,7 +83,6 @@ export const SecurityStep: Component<SecurityStepProps> = (props) => {
STORAGE_KEYS.SETUP_CREDENTIALS,
JSON.stringify({
username: username(),
password: finalPassword,
apiToken: token,
createdAt: new Date().toISOString(),
}),
@@ -17,18 +17,6 @@ interface TooltipProps extends TooltipOptions {
visible: boolean;
}
// Sanitize tooltip content to prevent XSS
function sanitizeContent(content: string): string {
// Remove any HTML tags and encode special characters
return content
.replace(/<[^>]*>/g, '') // Remove HTML tags
.replace(/&/g, '&amp;') // Encode ampersands
.replace(/</g, '&lt;') // Encode less than
.replace(/>/g, '&gt;') // Encode greater than
.replace(/"/g, '&quot;') // Encode quotes
.replace(/'/g, '&#x27;'); // Encode apostrophes
}
const Tooltip: Component<TooltipProps> = (props) => {
let tooltipRef: HTMLDivElement | undefined;
const [position, setPosition] = createSignal({ left: 0, top: 0 });
@@ -75,7 +63,7 @@ const Tooltip: Component<TooltipProps> = (props) => {
opacity: props.visible ? '1' : '0',
transition: 'opacity 120ms ease-out',
}}
textContent={sanitizeContent(props.content)}
textContent={props.content}
/>
</Portal>
</Show>
+4 -4
View File
@@ -3,7 +3,7 @@ const isDev = import.meta.env.DEV;
export const logger = {
debug: (message: string, data?: unknown) => {
if (isDev) console.log(`[DEBUG] ${message}`, data || '');
if (isDev) console.log('[DEBUG]', message, data ?? '');
},
info: (message: string, data?: unknown) => {
@@ -14,16 +14,16 @@ export const logger = {
message.includes('error') ||
message.includes('failed')
) {
console.log(`[INFO] ${message}`, data || '');
console.log('[INFO]', message, data ?? '');
}
},
warn: (message: string, data?: unknown) => {
console.warn(`[WARN] ${message}`, data || '');
console.warn('[WARN]', message, data ?? '');
},
error: (message: string, error?: unknown) => {
console.error(`[ERROR] ${message}`, error || '');
console.error('[ERROR]', message, error ?? '');
},
};
+1 -1
View File
@@ -5,7 +5,7 @@ type DisplayableNode = Pick<Node, 'name'> &
const sanitize = (value: string): string => value.trim().toLowerCase().replace(/[^a-z0-9]/g, '');
const escapeRegExp = (value: string): string => value.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\$&');
const escapeRegExp = (value: string): string => value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
const extractHostname = (value: string): string => {
if (!value) return '';
+9 -6
View File
@@ -1,5 +1,6 @@
import { spawn } from 'node:child_process';
import http from 'node:http';
import https from 'node:https';
// Add signal handlers to debug unexpected termination
const signals = ['SIGTERM', 'SIGINT', 'SIGHUP', 'SIGPIPE', 'SIGQUIT'];
@@ -60,10 +61,17 @@ const waitForHealth = async (healthURL, timeoutMs = 120_000) => {
console.log(`[pretest] Waiting for ${healthURL} to become healthy...`);
const startedAt = Date.now();
let attempt = 0;
const target = new URL(healthURL);
const client = target.protocol === 'https:' ? https : http;
const allowInsecureTLS = truthy(process.env.PULSE_E2E_INSECURE_TLS) && target.protocol === 'https:';
const agent = allowInsecureTLS ? new https.Agent({ rejectUnauthorized: false }) : undefined;
const checkHealth = () => {
return new Promise((resolve) => {
const req = http.get(healthURL, (res) => {
const req = client.get({
...target,
agent,
}, (res) => {
res.resume(); // Consume response data to free up memory
resolve(res.statusCode >= 200 && res.statusCode < 300);
});
@@ -91,11 +99,6 @@ const waitForHealth = async (healthURL, timeoutMs = 120_000) => {
throw new Error(`Timed out waiting for ${healthURL} after ${attempt} attempts`);
};
if (truthy(process.env.PULSE_E2E_INSECURE_TLS)) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
}
if (!shouldSkipPlaywrightInstall) {
await run(npxCmd, ['playwright', 'install', 'chromium']);
}