32 KiB
Security Policy
Supported Versions
We provide security updates for the following versions:
- Latest minor version: Active support (e.g., v0.20.x)
- Previous minor versions: Bug-fix support only
- Versions older than 3 minor versions: No support
Example:
- v0.39.0-0.39.x -- Full support (latest)
- v0.38.0-0.38.x -- Bug-fix support only
- v0.37.x and below -- No support
Reporting a Vulnerability
Do not open a public issue or pull request for security vulnerabilities.
Instead, use GitHub Security Advisories to report privately, or email vpn.alpamys@gmail.com with:
- Description: A clear explanation of the vulnerability
- Steps to Reproduce: How to trigger or demonstrate the issue
- Affected Versions: Which Soup versions are impacted
- Suggested Fix (optional): Any proposed solutions
- Contact Info: Your email for follow-up (optional)
What to Include
To: vpn.alpamys@gmail.com
Subject: Security Vulnerability Report: [Brief Title]
Description:
[Explain the vulnerability in detail]
Affected Component:
[e.g., data/loader.py, trainer/sft.py, etc.]
Steps to Reproduce:
1. [Step 1]
2. [Step 2]
3. ...
Impact:
[What could go wrong? Data exposure? RCE? DoS?]
Suggested Fix (optional):
[Your proposed solution, if any]
Response Timeline
- Initial Response: Within 48 hours
- Assessment: 1-3 business days
- Fix Development: Varies by severity
- Patch Release: As soon as possible after fix verification
- Public Disclosure: Coordinated with reporter (typically 90 days after patch release)
Severity Levels
- Critical: Remote code execution, data exposure, complete compromise (patch within 24-48 hours)
- High: Authentication bypass, privilege escalation, denial of service (patch within 1 week)
- Medium: Information disclosure, partial compromise (patch within 2 weeks)
- Low: Minor issues with limited impact (patch in next regular release)
Security Best Practices
When using Soup, follow these practices to stay secure:
1. Keep Soup Updated
pip install --upgrade soup-cli
2. Protect API Keys
Never commit API keys or secrets to version control. Use environment variables:
export HUGGINGFACE_TOKEN=your_token_here
export WANDB_API_KEY=your_key_here
soup train
3. Validate Data
- Only use trusted datasets
- Verify checksums for large datasets
- Inspect data for malicious content before training
4. Model Permissions
- Be cautious when downloading models from untrusted sources
- Use model hub providers with verified publishers (HuggingFace, Meta, etc.)
- Keep track of which models you've fine-tuned and their base model sources
5. GPU/Compute Safety
- Run on isolated machines if training on sensitive data
- Clear cache and temporary files after training
- Don't share fine-tuned models containing sensitive information
Known Vulnerabilities
We maintain a log of known security issues and their fixes. This will be updated as issues are discovered and resolved.
Current Status
No known critical vulnerabilities in current releases.
Security Hardening History
- v0.10.10: Bearer token auth on Web UI, CORS restrictions, path traversal protection, SSRF prevention, max_tokens limits, supply-chain pinning (llama.cpp b5270), deprecated CLI secret flags
- v0.12.0: experiment_name path traversal validation, GaLore parameter type enforcement
- v0.13.0: Batch inference max_tokens capped at 16384, trust_remote_code warning
- v0.14.0: Plaintext loader UTF-8 encoding, MoE config validation (moe_aux_loss_coeff ge=0, moe_lora boolean only)
- v0.14.3: Data validate auto-detects format, Web UI
--show-tokenflag + auth token documented - v0.15.0:
rope_scaling_typeLiteral constraint,max_lengthbounds (ge=64, le=1048576), FSDP config key allowlist, Liger Kernel exception handling narrowed - v0.16.0:
embedding_lossLiteral constraint,embedding_margingt=0 validation, ONNX export without trust_remote_code (with warning), TensorRT export subprocess list args (no shell injection), speculative decoding SSRF-protected (URL blocked) with warning panel, vLLM speculative model URL validation - v0.17.0: Server data generation provider SSRF validation (scheme whitelist + localhost-only HTTP), audio model trust_remote_code warning panel, audio file path traversal protection (resolved paths confined to audio_dir), SGLang backend trust_remote_code warning panel
- v0.18.0: Ollama deploy GGUF path traversal protection +
.ggufextension validation, model name validation (no path separators/null bytes), subprocess list args (no shell injection), Modelfile parameter key allowlist + value newline/null sanitization, overwrite warning panel - v0.19.0: Custom eval JSONL schema validation + 10k task cap, regex scoring ReDoS guard, judge API SSRF protection + API key isolation, human eval local-only terminal UI + 10k prompt cap, leaderboard read-only SQLite queries
- v0.20.0: Ollama provider localhost-only validation (remote blocked), Anthropic provider API key from env only (never CLI arg), vLLM provider SSRF protection (scheme whitelist + localhost-only HTTP), output path traversal protection (
..blocked), configurable rate limiting (--requests-per-minute) - v0.21.0: Migrate input/output path traversal protection (resolve + relative_to(cwd)), Unsloth .ipynb AST-only parsing (no exec/eval), recipes output path traversal protection, NEFTune config bounded (ge=0.0, le=50.0)
- v0.22.0: Multi-adapter serving path traversal protection (resolve + relative_to(cwd)), adapter name validation (alphanumeric + hyphens only), unknown adapter returns 404 (not 500)
- v0.23.0: AWQ/GPTQ calibration data path traversal protection (resolve + relative_to(cwd)), AWQ/GPTQ output path stays under cwd, curriculum_buckets bounded (ge=1, le=20), AWQ/GPTQ trust_remote_code warning panel
- v0.24.0: HF download trust_remote_code=False + warning panel, HF download output path sanitized (Path.name), download --samples capped at 1M, dataset registry name validation (no path separators/null bytes), registry path traversal protection, loss_watchdog threshold le=100 + patience le=1000, freeze_layers le=1000
- v0.24.1: AWQ/GPTQ output path traversal validation moved before import check (previously unreachable when autoawq/auto-gptq not installed), Windows Unicode fix for Rich console output (replaced non-ASCII symbols with ASCII equivalents)
- v0.24.2: Chat proxy SSRF protection (localhost-only HTTP, HTTPS for remote), chat proxy max_tokens capped at 16384 + temperature/top_p bounded, chat proxy Bearer token auth required, XSS prevention (HTML-escape before markdown render), runs compare max 5 runs, config from-form validates via load_config_from_string, SSE read endpoints no auth (GET)
- v0.25.0: Tool-calling JSON-only parsing (no eval), RLVR math_verify regex-extracted numerics (no eval), code_exec 5s timeout + 512MB RLIMIT on POSIX + ephemeral cwd + socket patch +
python -I -S+ 10KB output cap, verifiable_domain Literal constraint, LoRA PEFT mutual exclusion (DoRA/VeRA/OLoRA), data augment path containment + caps, forgetting_detection bounds, checkpoint_intelligence bounds + symlink refusal, autopilot path containment (realpath + commonpath) + goal Literal + GPU/time budget bounds, MLX trainers no trust_remote_code - v0.26.0 — Registry: name/tag validation (alphanumeric +
_-.only, null-byte rejected, name ≤128 / tag ≤64 chars), artifact path containment (defaultenforce_cwd=Trueviaos.path.realpath + commonpath, stored path is realpath), SQL LIKE wildcard escaping (%and_escaped withESCAPE '\\'insearch()and prefixresolve()), DB 600 perms on POSIX, lineage indirect-cycle detection (BFS ancestor walk before insert), CLI Rich markup escaped everywhere,resolve()raisesAmbiguousRefErroron ambiguous prefix (no silent None) - v0.26.0 — Eval Gate: suite path via shared
utils/paths.is_under_cwdcontainment,regression_threshold[0.0, 1.0],every_n_epochs[1, 100],on_regressionLiteral ("stop"/"warn"/"continue"),GateTask.tasks/promptsnull-byte rejection,judge_modelURL scheme allowlist (ollama://,https://,http://localhost/http://127.0.0.1) — SSRF hardening, callback fails-safe: structured errors treated as regressions underon_regression="stop" - v0.26.0 — Trace-to-Preference: input/output path containment via shared
is_under_cwd, trace line cap 100,000,--format/--signalLiteral validation, PII warning panel before every run, JSON-only parsing (no eval), malformed JSON lines skipped silently - v0.26.0 — Quant-Lobotomy:
--before/--after/--tasksall containment-checked,registry://refs support optionalkindsfilter to avoid picking the wrong artifact, format Literal validated - v0.26.0 — Soup Cans: Manifest format version pinned to 1; name alphanumeric+
_-.; author max 128 chars, no null bytes/newlines; created_at must parse viadatetime.fromisoformat; description max 4096; DataRef URL HTTPS-only; hf_dataset regex-validated; tar extraction usesfilter="data"on Python 3.12+, fallback only onTypeError/AttributeError(notTarError); manual symlink/hardlink rejection +commonpathcheck; 100 MB size cap on pack + fork; dunder-key (__*__) and null-byte rejection in fork modifications to prevent prototype pollution; inspect/read_config refuse paths outside cwd - v0.27.0 — Multi-GPU Mastery:
--gpusbounds (reject bool, non-digit, zero, negative, values aboveMAX_GPU_COUNT=128);--gpus autoon 0-GPU host prints explicit yellow warning (no silent no-op); Rich markup escaped on--configpath before embedding in the multi-GPU advice Panel;accelerate launchargv assembled viashlex.quoteper element (copy-pasted command safe against crafted paths);build_accelerate_argvvalidatesnum_processes >= 1,mixed_precisionLiteral (no/fp16/bf16/fp8),num_machinesbounded[1, 256]; ZeRO++ integer literals (int(1e9)not float) so DeepSpeed strict JSON validator accepts;validate_fsdp2_compile_configrequires FSDP + CUDA + transformers + torch>=2.2/accelerate>=0.27; DeepSpeed-MII stub exits non-zero to prevent silent mis-start;validate_pipeline_configenforcespipeline_stages >= 2+ CUDA +gpu_count >= stages;pipeline_stagesPydantic bounds[1, 16];parallelismLiteraldata|pipeline; NCCL env (NCCL_P2P_DISABLE/NCCL_IB_DISABLE/NCCL_NVLS_ENABLE) applied viaos.environ.setdefaultonly — user/launcher overrides are never stomped - v0.28.0 — Training Speed & Memory:
quantization_aware: Union[bool, Literal["fp8"]]rejects arbitrary strings (onlytrue/false/"fp8"); FP8 path requires CUDA + Hopper+ SM capability + transformers backend;gradient_checkpointing: Union[bool, Literal["selective","medium","full","auto"]]rejects unknown tier strings and returns only HF-supported keys (no private markers leak intoTrainingArguments.gradient_checkpointing_kwargs);activation_offloadingLiteralcpu|disk, scratchsave_dircontainment-enforced via sharedutils/paths.is_under_cwdbefore disk writes,torch.load(weights_only=True)prevents arbitrary Python deserialization on reload, TOCTOU closed betweenmkstempandtorch.saveby holding the fd open, best-effort cleanup on context exit (handles SIGKILL mid-backward);kernel_picker.pick_best_kernelraisesValueErrorwhen all candidates lack a finitetime_ms(prevents silent promotion of an untimed combo); Cut CE architecture detector matches on last path component only (sodeepseek-ai/...-phi-...org-prefix does not trigger a Phi patch on a DeepSeek model);build_cross_doc_masknumpy-vectorised to avoid O(seq_length²) pure-Python fill atmax_lengthbound (1M);@model_validatorrequirespacking=truewhenpacking_cross_doc_attn_mask=true(prevents silent no-op);SoupConfig._validate_v028_speed_memory_sft_onlyrejectsuse_cut_ce/quantization_aware="fp8"/kernel_auto_compose/activation_offloadingon non-SFT tasks — prevents legacy int8-QAT wrapper from crashing on the string"fp8"and prevents silent no-ops on DPO/GRPO/KTO/etc. (multi-trainer wiring tracked for v0.28.1) - v0.29.0 — HF Hub Deep Integration:
HF_ENDPOINTSSRF-hardened — scheme allowlist (http/https), null-byte rejection,0.0.0.0explicitly rejected, plain-HTTP only permitted for loopback (localhost/127.0.0.1/::1), RFC1918 / link-local / cloud-metadata (169.254.x) IPs rejected viaipaddress.ip_address; repo ID regex[A-Za-z0-9][A-Za-z0-9._-]{0,95}per component, ≤200 chars total, null-byte / whitespace /../ leading-/rejection (applied topush --repo,train --push-as,data push --hf-dataset,deploy hf-space --model/--space); collection slugowner/slug-hashregex-validated, ≤256 chars; HF token resolution single-sourced inutils/hf.resolve_token(env > cached login), explicit non-printable tokens rejected,push --tokenflag deprecated with yellow warning;soup push --modelconfined to cwd viais_under_cwd(prevents craftedsoup.yaml output:from uploading system files); auto-push checkpointallow_patternsrestricts uploaded files to*.safetensors/*.bin/*.pt/*.json/tokenizer*/trainer_state.json/training_args.bin/README.md(keeps.envand source files out of auto-pushed branches);prepare_hf_resumeenforces cwd containment and passeslocal_dir_use_symlinks=False(defeats symlink-based FS escape on olderhuggingface_hub); commit messages stripped to first line and capped at 200 chars (prevents multi-line injection into public HF commit history);_render_eval_scorecardneutralises|/[/]/(/)/!/newlines/tabs/</>in task names and non-numeric scores;data_lineageHTML-escaped (defeats XSS on HF Hub README viewer);render_space_templatevalidatesmodel_repoviavalidate_repo_idbefore substitution into renderedapp.py(crafted repo id cannot inject Python code);HFPushCallbackuses sticky_repo_failedflag to short-circuit retries after hard failure (no log spam, no wasted API calls);add_to_collectionprefers HfHubHTTPError 409 detection over string-match for duplicate handling - v0.30.0 — Inference Excellence: OTLP endpoint SSRF-hardened matching v0.29.0
HF_ENDPOINT(scheme allowlist,0.0.0.0rejected, RFC1918 / link-local / cloud-metadata viaipaddress.ip_address, plain HTTP loopback-only);pick_draft_modelrejects URL-scheme target names (http:///https:///file://), null bytes, names >200 chars;validate_regex_patternlength-capped at 2048 + null-byte rejection + must compile;validate_json_schemadict shape + 64KB serialised cap + requiredtypefield;--json-schemafile path confined to cwd via sharedutils/paths.is_under_cwd;--structured-output jsonrequires--json-schema(fail-fast prevents silent no-op); FastAPI/v1/adapters/activate/{name}pattern^[a-zA-Z0-9][a-zA-Z0-9\-]*$enforced before handler runs; activate/deactivate state protected bythreading.Lock(no race on concurrent hot-swap);/v1/adaptersresponse omits filesystem paths (names + active flag only); CORS on transformers backend restricted to loopback origins (^https?://(localhost|127\.0\.0\.1)(:\d+)?$) since hot-swap endpoints mutate state without auth;auto_quant.Candidatename regex + score[0.0, 1.0]+ finite-float check + non-negative latency;pick_bestgenerator-safe (materialises to list) so error-message count is accurate; OTel span context usescontextlib.ExitStackso__exit__sees real exception info (spans correctly marked error on HTTPException);record_latencyalways runs infinallyso tail-latency percentiles include failure paths;build_traceridempotent — only installs provider when current isProxyTracerProvider/NoOpTracerProvider(preserves operator-supplied instrumentation) - v0.32.0 — Training Stability & Auto-Tuning:
--find-lr-outputcontainment via sharedutils/paths.is_under_cwd(prevents writes outside cwd);save_lr_finder_reportrejects NaN / Infinity floats inlrs/lossesand serialises withallow_nan=False(keeps the report parser-safe);compute_lr_schedulerejects non-positivestart_lr, inverted ranges, andnum_stepsoutside[2, 10_000];pick_mixed_precisionrejects empty / null-byte / >200-char model names and resolves multi-version quirks (qwen2.5vsqwen2,phi-3.5vsphi-3) by longest-substring-first iteration so an added family can never accidentally make a more-specific entry dead code;compute_warmup_stepsclamps to[10, 1000]with aratio==0.0short-circuit matching HF Trainer's "no warmup" convention;SpikeRecoveryStrategyis@dataclass(frozen=True)(post-construction mutation cannot bypass validation),max_attempts ∈ [1, 10],lr_decay ∈ (0, 1),min_lr > 0; cross-validator_validate_spike_recovery_requires_watchdogrejectsloss_spike_recovery=true, loss_watchdog=falseat config-load (fails fast instead of never triggering);convergence_window ∈ [5, 10_000],convergence_rel_tol ∈ (0, 1],recommend_actionreusesdetect_plateauso plateau heuristic stays single-source-of-truth;GradAccumMonitor.recommend()caps doubledaccumatMAX_ACCUM=1024so a runaway advisory loop cannot blow up DataLoader prefetch;generate_configvalidates BOTH the YAML output path AND the embeddeddecisions["output"]field viais_under_cwd(closes the gap where a crafteddecisions["output"]="../../etc"would have silently propagated into the rendered YAML) - v0.34.0 — Observability & Dev UX:
.crashbundle generator (utils/crash.py) recursively redactshf_*/sk-*/Bearer …token-shaped strings in any capturedconfigand metric tail before serialisation, so a.crashfile shared on a public GitHub issue cannot leak credentials;output_diris reduced toos.path.basenameso$HOMEdoesn't leak;write_crash_bundleusesos.path.realpath + commonpathfor cwd containment (Windows-safe; raisesValueErrornotPermissionErrorso callers cannot silently swallow withexcept OSError); filename appendssecrets.token_hex(4)so two crashes in the same UTC second don't collide; bundle truncated toMAX_BUNDLE_BYTES=1_000_000.train.pycrash-write surfaces failures to the user (no silent missing-bundle).profiling.pyresolve_trace_pathrejects empty /./..///\\/ null-byterun_id(closes theoutput_dir/profiles/../trace.jsonescape) and usesos.path.realpath + is_under_cwd; profiles dir is created only on successful torch import (no stale empty dirs on torch-less CI).tracker.get_runLIKE-prefix match escapes%/_/\\and usesESCAPE '\\'so a craftedrun_idcannot widen the match (mirrors v0.26.0 registry policy). Lazy schema migration (_ensure_schema) tolerates the "duplicate column" race when two CLI processes start simultaneously on a fresh DB (fork-based multi-GPU training, TUI auto-refresh).runs.py show/replay/cleanswitched userrun_idrendering tomarkup_escapeand switchedcleancontainment from brokenPath.resolve() + relative_to()to project-standardos.path.realpath + is_under_cwd.tui_app.pylazy-importsExperimentTrackerandmarkup_escapes every DB-sourced string before passing into Textual widgets so a crafted base_model / experiment_name cannot inject[bold red]…[/]markup.run_cost.estimate_run_cost_usdrejectsboolinnum_gpus(bool is a subclass of int — same defence as v0.30.0Candidate.__post_init__); duration clamped to[0, 1 year]; unknown GPU returnsNoneso callers render—instead of fabricating$0.00.log_level.parse_log_levelrejects non-string + null-byte input. - v0.33.0 — Live Wire: RLVR
code_exec_rewardadds OS-level isolation (Linux best-effortos.unshare(CLONE_NEWUSER|CLONE_NEWNET|CLONE_NEWPID), macOSsandbox-execwith default-denyMACOS_SANDBOX_PROFILEnarrowed to a 3-namemach-lookupallowlist to prevent DNS / NSURLSession bypass of(deny network*));prune_checkpointsswitches to TOCTOU-safeos.lstat + S_ISLNK+shutil.rmtree(onerror=_abort_on_symlink)so a symlink encountered mid-walk aborts rather than escapes;run_gatewraps each task scorer in a typedtry/exceptso backend failures producescore=None, error=str(exc)(never silentscore=1.0);_parse_judge_urlremoves the barehttp://catch-all (defence-in-depth after the Pydantic GateTask validator);soup can runrequires--yesor explicit consent callback and raisesValueError(notPermissionError, which is anOSErrorsubclass that broadexceptblocks would swallow); GGUFrglobresult for ollama deploy isrealpath+commonpathchecked against extract_dir (prevents symlink escape from a crafted can);DeployTarget.pathvalidator normalises mixed\\//separators before splitting (closes a Windows..bypass);CAN_FORMAT_VERSION1→2 (additive — v1 still loads);soup can publishvalidatesrepo_idviautils/hf.validate_repo_id, resolves token viaresolve_token, sanitises commit messages (first-line, 200-char cap), uses HTTPS-only HfApi;_write_spike_recovery_hintaddsis_under_cwdcontainment check onargs.output_dirfrom raw HFTrainingArguments;lookup_entry_by_output_diremitsResourceWarningwhen 1000-row scan limit is hit (no silent miss);CrossDocCollatorno longer mutates input feature dicts (HF Dataset rows are cached and reused — mutation broke subsequent batches);Candidaterejectsboolinscore/latency_ms(was sneaking pastintisinstance check);evaluate_candidatelatency mean now divides by completed prompts (excludes crashed) so a broken candidate isn't artificially fast;auto_quant.run_auto_quant_pickersoft-falls-back to highest-scored candidate when no candidate clearsmin_score(server still binds);build_logits_processorsreturns[]when neitheroutlinesnorlm-format-enforceris installed (server degrades to free-form rather than 500); MII server uses loopback-only CORS, max_tokens cap [1, 16384], stream rejection, generic 500 with no stack-trace leak;os.execvpauto-reexec uses list args (no shell), all forwarded flags pre-validated;cleanup_extract_dirusesos.path.commonpath(Windows-safe) instead ofstartswith;_run_subprocesscatchesTimeoutExpiredand returns rc=124 (coreutils convention) instead of an unhandled traceback; neweval_resultsandtensorrtartifact kinds inRegistryStore._VALID_KINDS - v0.39.0 — LoRA Quality:
LoraConfig.init_strategy: Literal["random","pissa","olora"]rejects unknown strategies; PiSSA + DoRA / VeRA combinations rejected at config-load.model_validator(mode="before")alignsuse_olora=True→init_strategy="olora"via dict-copy (no caller mutation; matches v0.33.0 #47 immutability policy).rank_pattern/alpha_pattern: Optional[Dict[str, int]]capped at 256 keys × value (0, 1024], rejectsbool(subclass ofint— matches v0.30.0Candidatepolicy), null bytes in keys, empty keys; cross-validator rejects withuse_vera=True.ReLoRAPolicyis@dataclass(frozen=True)(post-construction mutation raisesFrozenInstanceError); bounds:steps ∈ [1, 1e7],warmup_ratio ∈ [0, 1],prune_ratio ∈ (0, 1)(strict — prevents zero-everything footgun).magnitude_prune_tensorstrict0 < prune_ratio < 1rejection, non-Tensor input raisesTypeError, empty / single-element tensor short-circuits (avoidskthvalue(_, 0)runtime crash)._validate_relora_supported_taskscross-validator rejectsrelora_stepswithtask != "sft"andbackend=mlxwith distinct error messages (matches v0.34.0 distinct-reason policy); multi-trainer expansion deferred to v0.39.1.is_gemma4_modeluses a word-boundary regex ((?:^|[^a-z0-9])gemma-?4(?:[^a-z0-9]|$)) so"ungemma4ed"/"my-gemma4ish"no longer over-match; null-byte rejection onmodel_name.apply_gemma4_clippable_patchweight-copy fallback logs at DEBUG instead of silent random-init; the patch is gated byis_gemma4_model(cfg.base)insft.pybefore invocation so non-Gemma4 trainings never traverse the module tree.apply_surgical_patchesrejects empty / null-bytemodel_namewithValueError.templates/load_templatecontainment: filename re-validated via_validate_name(rejects..///\\/null/empty);os.path.realpath + os.path.commonpathcontainment check on the resolved path against_templates_dir()so a tamperedmanifest.jsoncannot read files outside the package directory (mirrors v0.26.0 registry policy). Tampered-manifestValueErrorfrom_validate_namecaught and falls back to inline (no propagating exception). 256 KB file-size cap. InlineTEMPLATEScarries an explicit deprecation comment pointing at the canonical YAML registry;tests/test_templates_yaml.pyasserts byte-equality of all 16 inline ↔ YAML pairs to prevent silent drift. Planned removal: v0.41.0+. - v0.38.0 — Quant Menu:
TrainingConfig.quantizationLiteral extended withgptq/awq/hqq:1bit..hqq:8bit(nohqq:7bit— HQQ doesn't support it) /aqlm/eetq/mxfp4/fp8; Pydantic rejects every other string at config-load.validate_gptq_checkpointandvalidate_awq_checkpointprobe local paths forquantize_config.json/quant_config.json; HF repo IDs fall through; null-byte rejection + non-stringTypeErroron the ref._validate_prequantized_no_qatrejects every pre-quantized format combined withquantization_aware(int8 QAT or'fp8') — pre-quantized weights carry their own scale and QAT/FP8 prepare would silently corrupt them (mirrors LlamaFactoryquantization.py:117/199/211)._validate_bnb_quant_storage_only_with_4bitrejectsbnb_4bit_quant_storageon every non-BNB-4bit format (silent no-op otherwise); allowed dtypes:Literal["uint8", "float16", "bfloat16", "float32"]._validate_quant_menu_supported_tasksrestricts the new formats totask='sft'onbackend='transformers'in v0.38.0 with distinct MLX-backend vs unsupported-task error messages (matches v0.34.0 distinct-reason policy).check_quant_distributed_compathard-fails HQQ/EETQ/AQLM × {FSDP, ZeRO-3} (sourced from LlamaFactoryquantization.py:199/211plus AQLM dequant constraints); warning-tier (not error) for BNB-4bit + FSDP withoutbnb_4bit_quant_storageso users see the silent perf cliff; unknownquantizationraisesValueError(no silent pass) and the check is wired intocommands/train.pystartup.parse_hqq_bitsrejects unsupported bit-rates and malformedhqq:strings before any kernel build. - v0.37.0 — Multipack:
validate_multipack_architectureraisesValueErroron unknown arch (loud-fail vs Axolotl's silent-miss footgun); 18-arch frozen allowlist (Llama 3.x / Qwen 2/3 / Mistral / Gemma 2/3 / Phi 3/4 / DeepSeek V2/V3 / Mixtral / Falcon / StableLM / SmolLM2). FFD packer caps at_MAX_FFD_ITEMS=1_000_000(algorithm is O(N²) worst-case — defence against adversarial dataset DoS);boolrejection on every numeric input (max_len, per-elementlengths,batch_max_len,batch_size,seed,max_seq_length) matches v0.30.0+Candidatepolicy; generator-input materialisation prevents silent empty-bin output when validation exhausts the iterator.MultipackBatchSamplerrejects emptylengths, non-positivebatch_max_len/batch_size, items larger thanbatch_max_len.build_multipack_sampler_for_lengthsrejectstcfg.batch_size="auto"with actionable message (must be resolved upstream)._validate_multipack_packing_exclusivecross-validator onTrainingConfigrejects bothmultipackandpackingset;_validate_multipack_supported_tasksonSoupConfigrestricts multipack tosft/pretrainontransformersbackend with distinct error messages for MLX backend vs unsupported task (matches v0.34.0 distinct-reason policy).build_4d_attention_maskcaps allocations at_MAX_MASK_ELEMENTS=2³¹cells (~8GB float32) — defence againstmax_length=1M×batch_size=8OOM;tag_sub_sequencescapped at_MAX_BOUNDARY_SEGMENTS=1_000_000. Mask builder rejects non-floating dtypes (was silentnp.finfoValueError), non-2Dseq_pos_ids, negative segment IDs; padding (id=0) tokens are fully masked, including diagonal, so softmax is well-defined.select_packing_strategyrejects non-boolflash_attn_available.JinjaTemplateAnalyzerparses chat templates viaEnvironment.parseonly — never renders, so a crafted soup.yaml cannot trigger SSRF / filesystem reads; 128KB template cap, null-byte rejection,TemplateSyntaxErrorre-raised asValueError.DEFAULT_MESSAGE_FIELDSis afrozenset(runtime-immutable);JinjaTemplateAnalyzer.message_fieldsreturns a defensive copy. - v0.36.0 — Correctness First:
--trust-remote-codeopt-in replaces 9 unconditionaltrust_remote_code=Truecall sites acrosssoup train/chat/serve/data download/eval auto;KNOWN_SAFE_PREFIXESallowlist (15 first-party orgs) suppresses warning panel for trusted repos;model_requires_trust_remote_codeprobes localconfig.jsonforauto_map(HF Hub repo IDs returnNone/unknown — HF still raises loudly when custom code is actually needed);resolve_trust_remote_coderaisesValueErrorwith actionable message when model needs custom code but the user did not opt in. Chat-template hardening:DataConfig.chat_templatevalidator rejects null bytes, oversize (>64KB), AND filesystem-touching Jinja directives ({% include %},{% import %},{% from %},{% macro %},{% extends %}— both whitespace-control variants); empty string normalised toNone;_REGISTRYwrapped inMappingProxyType(callers cannot mutate);apply_chat_template_overrideemits yellow advisory when active so users knowsoup pushwill persist the override intotokenizer_config.json. SFT silent f-string fallback (f"{role}: {content}") replaced with hardValueError— produced wrong loss labels for years on tokenizers without a chat template. Loss-mask fallback passesadd_special_tokens=Falseto incremental tokenize calls so HF cannot double-prepend BOS at front of each render (consistent prefix-delta walk); narrowed exception catch in_apply_template_with_maskfrom(TypeError, ValueError)toTypeErroronly so a malformed messages list propagates instead of falling through to the loose path.SOUP_BATCH_CACHE_PATHenv override containment-checked viaos.path.realpath + commonpathagainst~/cwd/tempfile.gettempdir(); out-of-bounds values fall through to safe default; cache file gets best-effort0o600perms after atomic rename (matches v0.26.0 registry.db policy).make_cache_keyrejectsboolin numeric inputs (matches v0.30.0Candidatepolicy). Documented limitation: non-SFT trainers (DPO/GRPO/KTO/ORPO/SimPO/IPO/PPO/RewardModel/Pretrain/Embedding) andcommands/{diff,export,merge,infer,generate}.pystill hardcodetrust_remote_code=True— v0.36.x patch follow-up.
Security Scanning
- All code is scanned with
rufffor style and common issues - Dependencies are regularly updated to patch known CVEs
- GitHub's dependency scanning alerts us to vulnerable dependencies
- We use GitHub Actions CI/CD for continuous integration
Dependency Updates
We actively monitor and update dependencies:
- Major dependency updates: Tested in PR before merging
- Security patches: Applied immediately and released as patch versions
- Deprecated dependencies: Replaced proactively
Coming Soon
- Automated dependency scanning
- SBOM (Software Bill of Materials) for each release
- Third-party security audit (after 1.0.0 release)
Questions?
If you have security questions (not vulnerability reports) or need clarification:
- Open a GitHub Discussion tagged
security - Open a GitHub Issue (non-vulnerability inquiries)
- Check our CONTRIBUTING.md for general support
License
This Security Policy is provided under the Apache-2.0 license, same as the Soup project.
Last Updated: April 2026
For the latest version of this policy, visit: https://github.com/MakazhanAlpamys/Soup/blob/main/SECURITY.md