Add validation limits

This commit is contained in:
2026-09-26 17:06:09 +02:00
parent 2e01232afd
commit 32a108734a
2 changed files with 90 additions and 12 deletions
+24 -12
View File
@@ -1,19 +1,31 @@
Seed handling and input hardening (deferred from the B-list fixes)
=====================================================================
- Validate ranSeed in the GUI and startSequence: it must be an integer in a
safe range (e.g. -2^31 .. 2^31-1) if a fixed seed is requested. Blocks a
crafted share link from injecting a garbage seed value into the input deck.
- Derive per-scan-point seeds deterministically from the base seed and the
DONE - Validate ranSeed in the GUI and startSequence: it must be an integer in a
safe range (|v| <= 2^31-1, matching ISEED = IDINT(RI) in trimspNL.F). Enforced
in runSimulationSequence() (before any job is generated) AND in CreateInpFile()
(defense-in-depth), so a crafted share link or direct deck builder is blocked
with a "please change it" message. Non-numeric / NaN / non-integer / oversized
seeds no longer reach the engine.
DONE - Derive per-scan-point seeds deterministically from the base seed and the
scan index, so a multi-point run is reproducible in any finish order
(currently: initialSeed + iScan, but only when the user set a seed).
- Warn when a shared configuration fixed the RanSeed: every scan point then
uses the same generator, which can produce statistically misleading results.
- Cap client-controlled magnitudes: numberProj (projectile count) and
valEnergy must be upper-bounded from share links before the engine runs, to
prevent an accidental or malicious "run forever / write enormous .rge" job.
- Consider persisting the effective per-point seed back into the *.seq output
metadata so results self-document their RNG state.
(currently: initialSeed + iScan, only when the user set a valid seed).
DONE - Cap valEnergy magnitude at 60000 eV (positive incidence mode and the
Maxwellian transmission mode |E0| = temperature). Stopping fits (KDEE1=4) are
documented valid to ~50 keV for helium (meaning-of-params.txt:177-185), so 60
keV is the model's fitted ceiling. Applied to scanned energies and the base
value in runSimulationSequence() and CreateInpFile().
- Warn when a shared configuration fixed the RanSeed: every scan point then uses
the same generator, which can produce statistically misleading results.
- Cap client-controlled numberProj magnitude: an unbounded projectile count (e.g.
1e9) does not terminate within the 300 s watchdog and can flood scan points
(empirically verified: 1e9 did not finish in 15 s). GUI ProjNumberLimit() is
currently dead code (defined at TrimSPlib.js:3516, never wired to an event).
- Persisting the effective per-point seed: the seed already lives in each
archived *.inp (all three deck slots), so reproducibility does not require the
*.seq header change; extending *.seq would shift the 999(A20,1x) layer slots
and break getSequenceColumnInfo(). Surfacing it in run details instead is
deferred - not sure it is needed.
Deferred follow-ups from the C-list CGI hardening
=================================================
+66
View File
@@ -3634,6 +3634,16 @@ function setValues(content) {
function CreateInpFile() {
// Gate client-controlled magnitudes here too so a crafted config can never
// reach the engine, regardless of which caller builds the deck.
var inpSeedError = validateSeedString(All["ranSeed"]);
if (inpSeedError) {
throw new Error(inpSeedError);
}
var inpEnergyError = validateEnergyValue(All["valEnergy"]);
if (inpEnergyError) {
throw new Error(inpEnergyError);
}
// Create and return an input deck for the TRIM.SP-NL transport engine.
// Takes object All as input for all relevant parameters
@@ -3781,6 +3791,45 @@ var TRIMSP_WEB_MAX_CONCURRENCY = 4;
// an effectively unlimited list of jobs and freeze the tab.
var TRIMSP_MAX_SCAN_POINTS = 5000;
// Validated limits for client-owned input values. The Ziegler/ICRU stopping
// fits are only tuned up to ~50 keV for helium (KDEE1=4, see
// meaning-of-params.txt:177-185), so energies beyond 60 keV are outside the
// model's fitted validity. Negative energies select the Maxwellian initial
// distribution where |E0| is used as the ion temperature (trimspNL.F:806), so
// the same magnitude cap applies to both modes.
var TRIMSP_ENERGY_CAP_EV = 60000;
// The engine casts the deck seed to a signed 32-bit integer
// (ISEED = IDINT(RI), trimspNL.F:818-827), so any magnitude beyond that is
// silently truncated. Restrict the seed to |v| <= 2^31-1.
var TRIMSP_SEED_MAX_ABS = 2147483647;
// Returns an empty string when valid, otherwise a message that asks the user
// to change the offending value.
function validateEnergyValue(raw) {
var str = String(raw).trim();
var value = Number(str);
if (str === "" || !Number.isFinite(value)) {
return "Energy must be numeric. Please change it.";
}
if (Math.abs(value) > TRIMSP_ENERGY_CAP_EV) {
return "Energy magnitude must not exceed 60000 eV (the stopping fits are not validated beyond ~50 keV). Please change it.";
}
return "";
}
function validateSeedString(raw) {
var str = String(raw).trim();
if (str === "" || !/^[+-]?\d+$/.test(str)) {
return "Seed must be an integer. Please change it.";
}
var value = Number(str);
if (Math.abs(value) > TRIMSP_SEED_MAX_ABS) {
return "Seed is out of range (use an integer between -2147483648 and 2147483647). Please change it.";
}
return "";
}
// Expand a From/Step/To range into a list of scan values. Both ascending and
// descending steps are valid (e.g. scanning from high to low energy), so the
// old "loop while value <= to" form is replaced by a count-based expansion
@@ -3995,12 +4044,29 @@ async function runSimulationSequence() {
return(0);
}
// Cap the projectile energy magnitude before any job is generated. For an
// energy sweep the scan values are authoritative; for any other sweep the
// base valEnergy must still stay in the fitting range.
var evalues = (ScanAttrib === "valEnergy") ? SValues : [Number(All["valEnergy"])];
for (var evIndex = 0; evIndex < evalues.length; evIndex++) {
var energyError = validateEnergyValue(evalues[evIndex]);
if (energyError) {
updateStatusBar({ run: energyError });
return(0);
}
}
// Freeze the initial GUI state and expand the scan into independent jobs.
// Each job gets its own input file name and a deterministic seed offset so
// the results are reproducible even when runs finish out of order.
const scanValues = SValues.slice();
const initialAll = Object.assign({}, All);
const initialSeed = parseInt(initialAll["ranSeed"], 10);
const seedError = validateSeedString(initialAll["ranSeed"]);
if (seedError) {
updateStatusBar({ run: seedError });
return(0);
}
let jobs = [];
for (let iScan = 0; iScan < scanValues.length; iScan++) {