mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Fix code scanning findings
This commit is contained in:
@@ -9,6 +9,9 @@ on:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
secret-scan:
|
||||
name: Secret Scan
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,9 @@ on:
|
||||
# schedule:
|
||||
# - cron: '0 0 * * *' # Nightly at midnight
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,6 +16,9 @@ on:
|
||||
- "README.md"
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint and Render Chart
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, '&') // Encode ampersands
|
||||
.replace(/</g, '<') // Encode less than
|
||||
.replace(/>/g, '>') // Encode greater than
|
||||
.replace(/"/g, '"') // Encode quotes
|
||||
.replace(/'/g, '''); // 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>
|
||||
|
||||
@@ -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 ?? '');
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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 '';
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user