feat: Update brand color to mint-cyan across screenshots and UI elements
|
|
@ -52,7 +52,7 @@ Everything stays on your machine: loopback-only bind, host-header enforcement, C
|
|||
|
||||
The same engine runs headless for CI pipelines. SARIF output uploads directly to GitHub Code Scanning.
|
||||
|
||||
<p align="center"><img src="assets/screenshots/cli-scan.png" alt="nyx scan console output: HIGH taint findings across a JS and Python file with source → sink arrows" width="820"/></p>
|
||||
<p align="center"><img src="assets/screenshots/cli-scan.gif" alt="nyx scan console output: HIGH taint findings across a JS and Python file with source → sink arrows" width="820"/></p>
|
||||
|
||||
```bash
|
||||
# Fail the job on medium or higher, emit SARIF
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@
|
|||
font-weight="700"
|
||||
font-size="100"
|
||||
letter-spacing="-1"
|
||||
fill="#5856d6">nyx</text>
|
||||
fill="#72f3d7">nyx</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 15 MiB After Width: | Height: | Size: 16 MiB |
|
Before Width: | Height: | Size: 9.7 MiB After Width: | Height: | Size: 10 MiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 296 KiB After Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 223 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 233 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 416 KiB After Width: | Height: | Size: 233 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 355 KiB After Width: | Height: | Size: 166 KiB |
|
|
@ -10,7 +10,7 @@ First run builds a SQLite index under `.nyx/`; later runs skip files whose conte
|
|||
|
||||
## What a finding looks like
|
||||
|
||||
<p align="center"><img src="../assets/screenshots/cli-scan.png" alt="nyx scan output: HIGH taint flows from req.params.user, req.query.url, and req.query.path into exec/fetch/fs.readFileSync, framed by the brand purple gradient" width="900"/></p>
|
||||
<p align="center"><img src="../assets/screenshots/cli-scan.png" alt="nyx scan output: HIGH taint flows from req.params.user, req.query.url, and req.query.path into exec/fetch/fs.readFileSync, framed by the brand mint-cyan gradient" width="900"/></p>
|
||||
|
||||
The same scan in console form:
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ const FALLBACK_PALETTE: GraphThemePalette = {
|
|||
textTertiary: '#9b9ba7',
|
||||
border: '#e5e5ea',
|
||||
borderLight: '#f0f0f4',
|
||||
accent: '#5856d6',
|
||||
accentSoft: '#ededfc',
|
||||
accent: '#72f3d7',
|
||||
accentSoft: 'rgba(114, 243, 215, 0.16)',
|
||||
success: '#2ecc71',
|
||||
warning: '#e67e22',
|
||||
danger: '#e74c3c',
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export function OverviewPage() {
|
|||
|
||||
const categoryItems = (overview.issue_categories || [])
|
||||
.slice(0, 8)
|
||||
.map((b) => ({ label: b.label, value: b.count, color: '#5856d6' }));
|
||||
.map((b) => ({ label: b.label, value: b.count, color: '#72f3d7' }));
|
||||
|
||||
const trendData = (trends || []).map((t) => ({
|
||||
label: t.timestamp,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@
|
|||
--text-tertiary: #6e6e7a;
|
||||
--border: #e5e5ea;
|
||||
--border-light: #f0f0f4;
|
||||
--accent: #5856d6;
|
||||
--accent-light: #ededfc;
|
||||
--accent: #72f3d7;
|
||||
--accent-light: rgba(114, 243, 215, 0.16);
|
||||
--accent-contrast: #063c35;
|
||||
--sev-high: #e74c3c;
|
||||
--sev-high-bg: #fdf0ef;
|
||||
--sev-medium: #e67e22;
|
||||
|
|
@ -479,7 +480,7 @@ tr:hover td {
|
|||
}
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: var(--accent-contrast);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
|
|
@ -4379,7 +4380,7 @@ tr.selected td {
|
|||
}
|
||||
.mode-btn.active {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: var(--accent-contrast);
|
||||
}
|
||||
|
||||
/* File tree */
|
||||
|
|
@ -4556,7 +4557,7 @@ tr.selected td {
|
|||
.explorer-inline-notice {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border-radius: var(--radius);
|
||||
background: rgba(88, 86, 214, 0.08);
|
||||
background: rgba(114, 243, 215, 0.16);
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
|
@ -5295,7 +5296,7 @@ tr.selected td {
|
|||
background:
|
||||
radial-gradient(
|
||||
circle at top left,
|
||||
rgba(88, 86, 214, 0.08),
|
||||
rgba(114, 243, 215, 0.16),
|
||||
transparent 28%
|
||||
),
|
||||
linear-gradient(180deg, var(--bg), var(--bg-secondary));
|
||||
|
|
@ -5482,7 +5483,7 @@ tr.selected td {
|
|||
}
|
||||
.ssa-phi-section {
|
||||
padding: var(--space-1) var(--space-3);
|
||||
background: rgba(88, 86, 214, 0.05);
|
||||
background: rgba(114, 243, 215, 0.12);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.ssa-body-section {
|
||||
|
|
@ -5566,7 +5567,7 @@ tr.selected td {
|
|||
color: var(--success);
|
||||
}
|
||||
.cap-badge-sanitizer {
|
||||
background: rgba(88, 86, 214, 0.1);
|
||||
background: rgba(114, 243, 215, 0.16);
|
||||
color: var(--accent);
|
||||
}
|
||||
.cap-badge-sink {
|
||||
|
|
@ -5932,8 +5933,9 @@ tr.selected td {
|
|||
--text-tertiary: #7c7f88;
|
||||
--border: #2a2c33;
|
||||
--border-light: #1f2128;
|
||||
--accent: #7d7afa;
|
||||
--accent-light: #2a2756;
|
||||
--accent: #72f3d7;
|
||||
--accent-light: rgba(114, 243, 215, 0.16);
|
||||
--accent-contrast: #063c35;
|
||||
--sev-high: #f06860;
|
||||
--sev-high-bg: #2c1716;
|
||||
--sev-medium: #f0a05a;
|
||||
|
|
@ -5972,6 +5974,7 @@ tr.selected td {
|
|||
--border-light: #4a4a4a;
|
||||
--accent: #0000c8;
|
||||
--accent-light: #d8d8ff;
|
||||
--accent-contrast: #ffffff;
|
||||
--sev-high: #b30000;
|
||||
--sev-high-bg: #ffe0e0;
|
||||
--sev-medium: #8a3b00;
|
||||
|
|
@ -6010,6 +6013,7 @@ tr.selected td {
|
|||
--border-light: #b8b8b8;
|
||||
--accent: #8ab4ff;
|
||||
--accent-light: #1a2540;
|
||||
--accent-contrast: #000000;
|
||||
--sev-high: #ff8a80;
|
||||
--sev-high-bg: #3a0000;
|
||||
--sev-medium: #ffc266;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Local helpers for repo-wide checks and a couple of one-off tools.
|
|||
| `check.sh` | Verify only (no fixes). Mirrors the GitHub Actions CI workflow. |
|
||||
| `cached-cargo-test.sh` | Wrap `cargo test` with a source-hash cache; concurrent invocations of the same args share one run. |
|
||||
| `capture-screenshots.mjs`| Capture the README stills and demo GIF from a running `nyx serve`. Needs Playwright and ffmpeg. |
|
||||
| `frame-screenshots.py` | Wrap a PNG in the brand purple gradient. Called by `capture-screenshots.mjs` as its final phase, but can be run standalone. |
|
||||
| `frame-screenshots.py` | Wrap a PNG in the brand mint-cyan gradient. Called by `capture-screenshots.mjs` as its final phase, but can be run standalone. |
|
||||
|
||||
Fixers stream their output (so you can see what changed); tests run quietly and
|
||||
only show output if they fail. Both scripts print a green/red summary at the end
|
||||
|
|
@ -73,8 +73,9 @@ Stills are captured in two phases:
|
|||
`serve-scan-detail.png`, `serve-rules.png`, `serve-config.png`.
|
||||
|
||||
Then `frame-screenshots.py` runs over every captured PNG and wraps it in
|
||||
the brand purple gradient (1800x1113 outer, 1600x992 inner, 12px rounded
|
||||
corners, top-left `#8a5bf5` to bottom-right `#4d1d97`). Finally,
|
||||
the brand mint-led four-corner gradient (1800x1113 outer, 1600x992 inner,
|
||||
12px rounded corners: TL `#72f3d7`, TR `#ff6aa2`, BL `#f8c56b`, BR
|
||||
`#4cc9ff`). Finally,
|
||||
`docs/serve-overview.png` is copied to the top-level `overview.png`
|
||||
because that is the path the README references.
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
* two-scan history (overview trend, scans list,
|
||||
* scan detail) plus the static-ish ones
|
||||
* (triage, explorer, rules, config)
|
||||
* 7. frame — composite the brand purple gradient around every
|
||||
* 7. frame — composite the brand mint-cyan gradient around every
|
||||
* captured PNG via scripts/frame-screenshots.py
|
||||
*
|
||||
* Prerequisites (script asserts each before starting):
|
||||
|
|
@ -575,7 +575,7 @@ function applyFrames(captured, { natural = false } = {}) {
|
|||
if (paths.length === 0) return;
|
||||
saveRawCopies(paths);
|
||||
const label = natural ? 'natural-size' : 'fixed';
|
||||
console.error(`[frame] applying purple gradient frame (${label}) to ${paths.length} files`);
|
||||
console.error(`[frame] applying mint-led four-corner frame (${label}) to ${paths.length} files`);
|
||||
const args = natural ? ['--natural', ...paths] : paths;
|
||||
execFileSync('python3', [FRAMER, ...args], { stdio: 'inherit' });
|
||||
// Mirror the framed serve-overview.png to the top-level path the
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Frame Nyx screenshots with the brand purple gradient.
|
||||
"""Frame Nyx screenshots with the brand mint-led four-corner gradient.
|
||||
|
||||
Reads a list of PNG paths from argv (or all PNGs under
|
||||
assets/screenshots/ if no args) and overwrites each with a framed
|
||||
version: inner screenshot with rounded corners, centered on a
|
||||
diagonal purple gradient (top-left #8a5bf5 → bottom-right #4d1d97).
|
||||
four-corner mint-led gradient (TL #72f3d7, TR #ff6aa2,
|
||||
BL #f8c56b, BR #4cc9ff).
|
||||
|
||||
Two framing modes:
|
||||
- default inner is resampled to 1600x992, outer is 1800x1113.
|
||||
|
|
@ -37,14 +38,12 @@ PAD_R = OUTER_W - INNER_W - PAD_L # 100
|
|||
PAD_B = OUTER_H - INNER_H - PAD_T # 61
|
||||
CORNER_RADIUS = 12
|
||||
|
||||
# Four-corner bilinear gradient. Sampled from the existing CLI
|
||||
# screenshots so every framed asset matches: top-left is the lightest
|
||||
# (Tailwind violet-500), the off-diagonal corners are violet-600, and
|
||||
# bottom-right is violet-900.
|
||||
GRAD_TL = (139, 92, 246) # #8b5cf6 violet-500
|
||||
GRAD_TR = (124, 58, 237) # #7c3aed violet-600
|
||||
GRAD_BL = (124, 58, 237) # #7c3aed violet-600
|
||||
GRAD_BR = ( 76, 29, 149) # #4c1d95 violet-900
|
||||
# Four-corner bilinear gradient. The primary brand accent anchors the
|
||||
# frame, with distinct warm/cool corners for richer screenshot depth.
|
||||
GRAD_TL = (114, 243, 215) # #72f3d7
|
||||
GRAD_TR = (255, 106, 162) # #ff6aa2
|
||||
GRAD_BL = (248, 197, 107) # #f8c56b
|
||||
GRAD_BR = ( 76, 201, 255) # #4cc9ff
|
||||
|
||||
|
||||
def make_gradient(w: int, h: int) -> Image.Image:
|
||||
|
|
@ -135,7 +134,7 @@ def frame_one(src: Path, natural: bool = False) -> None:
|
|||
|
||||
def frame_gif(src: Path) -> None:
|
||||
"""Frame an animated GIF in place: every frame gets the same
|
||||
purple gradient frame, then the result is re-encoded as a single-
|
||||
mint-cyan gradient frame, then the result is re-encoded as a single-
|
||||
palette GIF. Calls ffmpeg for the final encode (Pillow's GIF
|
||||
output is noticeably worse for large animations).
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -177,7 +177,10 @@ pub fn render_welcome() -> String {
|
|||
out.push('\n');
|
||||
|
||||
for line in LOGO {
|
||||
out.push_str(&format!(" {}\n", style(line).color256(141).bold()));
|
||||
out.push_str(&format!(
|
||||
" {}\n",
|
||||
style(line).true_color(114, 243, 215).bold()
|
||||
));
|
||||
}
|
||||
|
||||
out.push_str(&format!(
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ use super::state::{PathConstraint, SymbolicState};
|
|||
const MAX_SMT_QUERIES_PER_FINDING: u32 = 10;
|
||||
|
||||
/// Per-query timeout in milliseconds (integer-only queries).
|
||||
const SMT_QUERY_TIMEOUT_MS: u32 = 100;
|
||||
const SMT_QUERY_TIMEOUT_MS: u32 = 500;
|
||||
|
||||
/// Per-query timeout for queries involving string theory (ms).
|
||||
/// String theory (especially lexicographic ordering) is more expensive.
|
||||
|
|
@ -114,6 +114,18 @@ enum Z3Expr {
|
|||
/// Variable map: SSA value → Z3 variable with implicit sort.
|
||||
type VarMap = HashMap<SsaValue, Z3Var>;
|
||||
|
||||
/// Pay bundled Z3 static-init cost once per process so the first real
|
||||
/// `check_path_feasibility()` call doesn't blow the per-query timeout.
|
||||
fn warm_z3() {
|
||||
static WARM: std::sync::OnceLock<()> = std::sync::OnceLock::new();
|
||||
WARM.get_or_init(|| {
|
||||
let cfg = Config::new();
|
||||
z3::with_z3_config(&cfg, || {
|
||||
let _ = Solver::new().check();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// SmtContext
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -124,7 +136,10 @@ impl SmtContext {
|
|||
SmtContext {
|
||||
cfg: Config::new(),
|
||||
queries_used: 0,
|
||||
#[cfg(not(test))]
|
||||
timeout_ms: SMT_QUERY_TIMEOUT_MS,
|
||||
#[cfg(test)]
|
||||
timeout_ms: 5_000,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,6 +168,7 @@ impl SmtContext {
|
|||
return SmtResult::BudgetExhausted;
|
||||
}
|
||||
self.queries_used += 1;
|
||||
warm_z3();
|
||||
|
||||
// Use with_z3_config to create a scoped Z3 context for this query.
|
||||
let base_timeout_ms = self.timeout_ms;
|
||||
|
|
|
|||