diff --git a/skills/ai-security/model-supply-chain/SKILL.md b/skills/ai-security/model-supply-chain/SKILL.md index 20531bc3..44a91462 100644 --- a/skills/ai-security/model-supply-chain/SKILL.md +++ b/skills/ai-security/model-supply-chain/SKILL.md @@ -14,7 +14,7 @@ phase: [build, review, operate] frameworks: [OWASP-LLM03-2025, SLSA-v1.0, MITRE-ATLAS] difficulty: advanced time_estimate: "45-90min" -version: "1.0.0" +version: "1.0.1" author: unitoneai license: MIT allowed-tools: Read, Grep, Glob @@ -131,6 +131,30 @@ Glob: **/config.json | No model card or provenance documentation available | Medium | | Checksums verified but against values stored in the same repository as the model (self-referential) | Medium | +#### Adapter Composition Evidence Gate + +When LoRA, QLoRA, PEFT, prompt adapters, or other adapter/plugin weights are deployed, assess the deployed unit as the complete composition. A signed adapter alone is not enough: model behavior depends on the base model revision, tokenizer/config, quantization, target modules, merge order, runtime framework, and safety evaluation performed after composition. + +**Required adapter composition gates:** + +| Gate | Required evidence | Fail if | +|---|---|---| +| `MSC-COMP-01` | Composition manifest identifies the deployed unit and binds base model source, revision, checksum, license, and model card to each adapter source, revision, checksum, license, training summary, and adapter card. | Base model is `latest`, adapter metadata is missing, or the adapter is reviewed without the deployed base model. | +| `MSC-COMP-02` | Tokenizer, processor, config, special tokens, chat template, generation config, framework versions, and runtime loader versions are pinned with revisions or checksums. | Tokenizer/config can drift independently from the base or adapter, or framework versions are omitted. | +| `MSC-COMP-03` | Merge/load parameters are recorded: LoRA/QLoRA/PEFT type, rank, alpha, target modules, adapter weights path, quantization bits/scheme/calibration, merge order, merge script, and tool version. | The same adapter could be loaded into different modules, quantization modes, or merge orders with no auditable record. | +| `MSC-COMP-04` | Approved compatibility constraints list allowed base-adapter-tokenizer-quantization combinations and disallowed pairs, including model family, architecture, context length, and license constraints. | Any adapter can be hot-loaded into any base model, or architecture/license incompatibilities are not enforced. | +| `MSC-COMP-05` | Base and adapter trust evidence comes from signatures, attestations, registry digests, or independently computed trusted checksums that are not self-referential to the same compromised source. | Checksums are only copied from the same registry/repo as the artifact, or no independent trust root exists. | +| `MSC-COMP-06` | The composed bundle has a signed manifest and reproducible merge/load job showing immutable inputs, builder identity, environment, script version, and output digest. | Production serves a manually assembled or mutable composition that cannot be rebuilt from recorded inputs. | +| `MSC-COMP-07` | Safety, regression, domain, jailbreak, and output-difference evaluations run on the composed deployed unit, not only on the base model or standalone adapter. | Evidence only covers standard benchmarks, base-model tests, or adapter-only metadata. | +| `MSC-COMP-08` | Promotion binds deployment to the signed composition and includes rollback bundle, canary/monitoring plan, owner approval, and reconstruction evidence. | Rollback points to an unrelated base model, or the deployed composition cannot be reconstructed. | + +**Status and severity guidance:** + +- Mark adapter deployments as `Not Evaluable` when the deployed composition cannot be reconstructed from immutable manifest inputs. +- Treat missing composition binding as **High** severity for production or safety-sensitive deployments, even when individual adapter files are signed. +- Cap confidence at **Low** when safety evaluation is not performed on the exact base-adapter-tokenizer-runtime combination. +- Escalate to **Critical** if an untrusted adapter can be hot-loaded into a privileged production model path without compatibility checks, approval, or rollback. + --- ### Step 2 -- Training Data Lineage @@ -382,10 +406,29 @@ Assess whether architectural and procedural controls exist to detect model backd |---|---|---|---|---|---| | [name] | [source] | [format] | [Yes/No] | [Yes/No] | [Complete/Partial/Missing] | +## Adapter Composition Inventory + +| Composition ID | Base Model | Adapter(s) | Tokenizer/Config | Quantization/Merge Order | Signatures/Attestations | Safety Eval | Rollback Bundle | Status | +|---|---|---|---|---|---|---|---|---| +| [composition] | [source/revision/checksum] | [source/revision/checksum] | [pinned/missing] | [recorded/missing] | [verified/missing/self-referential] | [composed/base-only/missing] | [available/missing] | [Pass/Fail/Not Evaluable] | + +## Adapter Composition Gate Results + +| Gate | Evidence Reviewed | Status | Risk | +|---|---|---|---| +| `MSC-COMP-01` | [composition manifest base/adapter binding] | [Pass/Fail/Not Evaluable] | [risk] | +| `MSC-COMP-02` | [tokenizer/config/runtime pinning] | [Pass/Fail/Not Evaluable] | [risk] | +| `MSC-COMP-03` | [merge/load/quantization parameters] | [Pass/Fail/Not Evaluable] | [risk] | +| `MSC-COMP-04` | [compatibility constraints and disallowed pairs] | [Pass/Fail/Not Evaluable] | [risk] | +| `MSC-COMP-05` | [signatures, attestations, or trusted checksums] | [Pass/Fail/Not Evaluable] | [risk] | +| `MSC-COMP-06` | [signed composition manifest and reproducible job] | [Pass/Fail/Not Evaluable] | [risk] | +| `MSC-COMP-07` | [composed-unit safety/regression evidence] | [Pass/Fail/Not Evaluable] | [risk] | +| `MSC-COMP-08` | [deployment binding, rollback, monitoring, reconstruction] | [Pass/Fail/Not Evaluable] | [risk] | + ## Findings ### Finding [N]: [Title] -- **Category:** [Provenance | Training Data | Fine-Tuning Pipeline | Inference Dependency | Model Card | Backdoor Detection] +- **Category:** [Provenance | Adapter Composition | Training Data | Fine-Tuning Pipeline | Inference Dependency | Model Card | Backdoor Detection] - **Severity:** [Critical | High | Medium | Low | Informational] - **OWASP LLM Category:** LLM03:2025 -- Supply Chain Vulnerabilities - **MITRE ATLAS Technique:** [technique ID and name] @@ -401,6 +444,7 @@ Assess whether architectural and procedural controls exist to detect model backd | Domain | Current State | Target State | Gap Severity | |---|---|---|---| | Model provenance | [description] | [recommendation] | [severity] | +| Adapter composition | [description] | [recommendation] | [severity] | | Training data lineage | [description] | [recommendation] | [severity] | | Fine-tuning pipeline | [description] | [recommendation] | [severity] | | Inference dependencies | [description] | [recommendation] | [severity] | @@ -441,6 +485,8 @@ Assess whether architectural and procedural controls exist to detect model backd 5. **Evaluating models only on benchmarks.** Standard benchmarks measure general capability, not supply chain integrity. A backdoored model will perform normally on benchmarks by design. Behavioral differential testing with curated, domain-specific test sets that probe for targeted manipulation is required to surface backdoors. +6. **Treating a signed adapter as safe outside its composition.** LoRA, QLoRA, and PEFT adapters materially change model behavior only after they are loaded or merged with a specific base model, tokenizer/config, quantization mode, framework version, and merge order. A valid adapter signature does not prove that the deployed composition is approved, compatible, reproducible, safe, or rollback-ready. + --- ## References diff --git a/skills/ai-security/model-supply-chain/tests/benign/signed_adapter_composition_manifest_and_safety_eval.json b/skills/ai-security/model-supply-chain/tests/benign/signed_adapter_composition_manifest_and_safety_eval.json new file mode 100644 index 00000000..7b398bff --- /dev/null +++ b/skills/ai-security/model-supply-chain/tests/benign/signed_adapter_composition_manifest_and_safety_eval.json @@ -0,0 +1,167 @@ +{ + "fixture": "signed_adapter_composition_manifest_and_safety_eval", + "skill": "model-supply-chain", + "description": "Benign fixture for a production LoRA composition with pinned base, adapter, tokenizer, quantization, signed bundle provenance, composed-unit safety evaluation, and rollback evidence.", + "deployment": { + "composition_id": "clinical-summary-v4-lora-20260609", + "environment": "production", + "owner": "ml-platform", + "serving_stack": "text-generation-inference", + "promotion_ticket": "MLPROMO-4821" + }, + "composition_manifest": { + "manifest_id": "composition-manifest-clinical-summary-v4", + "manifest_version": "2026.06.09", + "signed": true, + "signature": { + "type": "sigstore", + "issuer": "internal-fulcio", + "bundle_ref": "sigstore-bundle-composition-clinical-v4" + }, + "base_model": { + "source": "internal-registry/acme-clinical-llm", + "revision": "rev-clinical-base-2026-05-30", + "checksum": "sha256-example-base-clinical-20260609", + "license": "Apache-2.0", + "model_card": "model-cards/acme-clinical-llm-v4.md", + "attestation": "slsa-attestation-base-clinical-v4" + }, + "adapters": [ + { + "name": "clinical-summarization-lora", + "source": "internal-registry/adapters/clinical-summarization-lora", + "revision": "rev-adapter-2026-06-01", + "checksum": "sha256-example-adapter-clinical-20260609", + "license": "Apache-2.0", + "adapter_card": "adapter-cards/clinical-summarization-lora.md", + "training_summary": "training-summaries/clinical-summarization-lora-2026-06-01.md", + "attestation": "slsa-attestation-adapter-clinical-v2" + } + ], + "tokenizer_and_config": { + "tokenizer_source": "internal-registry/acme-clinical-tokenizer", + "tokenizer_revision": "rev-tokenizer-2026-05-30", + "tokenizer_checksum": "sha256-example-tokenizer-clinical-20260609", + "special_tokens_revision": "rev-special-tokens-2026-05-30", + "chat_template_revision": "rev-chat-template-2026-05-30", + "generation_config_revision": "rev-generation-config-2026-06-02", + "framework_versions": { + "transformers": "4.52.1", + "peft": "0.15.2", + "bitsandbytes": "0.45.5", + "torch": "2.7.0" + } + }, + "merge_and_runtime": { + "adapter_type": "LoRA", + "rank": 16, + "alpha": 32, + "target_modules": [ + "q_proj", + "k_proj", + "v_proj", + "o_proj" + ], + "adapter_weights_path": "registry://adapters/clinical-summarization-lora/rev-adapter-2026-06-01", + "quantization": { + "mode": "QLoRA", + "bits": 4, + "scheme": "nf4", + "calibration_dataset": "clinical-calibration-snapshot-2026-05" + }, + "merge_order": [ + "base-model", + "clinical-summarization-lora" + ], + "merge_script": "tools/model_build/merge_lora.py", + "merge_script_version": "rev-merge-tool-2026-06-03", + "loader_policy": "hot-load-disabled-after-signed-bundle-build" + }, + "compatibility_constraints": { + "approved_pairs": [ + "acme-clinical-llm@rev-clinical-base-2026-05-30 + clinical-summarization-lora@rev-adapter-2026-06-01" + ], + "disallowed_pairs": [ + "public-chat-base@latest + clinical-summarization-lora", + "acme-clinical-llm@rev-clinical-base-2026-05-30 + finance-sentiment-lora" + ], + "model_family": "acme-clinical", + "architecture": "decoder-only-transformer", + "context_length": 8192, + "license_check": "passed" + } + }, + "reproducible_build": { + "builder_identity": "github-actions-oidc:model-bundle-builder", + "job_id": "model-bundle-build-20260609-0421", + "immutable_inputs": true, + "output_bundle_digest": "sha256-example-composed-bundle-clinical-20260609", + "rebuild_verification": "matched-output-digest", + "registry_digest": "registry-digest-composed-clinical-v4" + }, + "safety_evaluation": { + "evaluated_artifact": "clinical-summary-v4-lora-20260609", + "matches_deployed_composition": true, + "standard_benchmarks": "passed", + "domain_regression_suite": "passed", + "safety_probe_suite": "passed", + "jailbreak_regression": "passed", + "known_good_output_diff": "within-approved-threshold", + "approval": "safety-review-accepted-2026-06-08" + }, + "deployment_controls": { + "deployment_binding": "serving-config pins composition-manifest-clinical-summary-v4", + "canary_plan": "5-percent-for-24-hours-with-safety-alerts", + "monitoring": "output-drift-and-policy-violation-alerts-enabled", + "rollback_bundle": "clinical-summary-v3-signed-rollback-bundle", + "reconstruction_test": "passed" + }, + "expected_gate_results": [ + { + "gate": "MSC-COMP-01", + "status": "Pass", + "evidence": "Manifest binds deployed composition to pinned base and adapter metadata, checksums, licenses, model card, adapter card, and training summary." + }, + { + "gate": "MSC-COMP-02", + "status": "Pass", + "evidence": "Tokenizer, special tokens, chat template, generation config, and framework versions are pinned." + }, + { + "gate": "MSC-COMP-03", + "status": "Pass", + "evidence": "LoRA rank, alpha, target modules, quantization, merge order, adapter path, merge script, and tool version are recorded." + }, + { + "gate": "MSC-COMP-04", + "status": "Pass", + "evidence": "Approved and disallowed composition pairs enforce base-adapter-tokenizer-quantization compatibility." + }, + { + "gate": "MSC-COMP-05", + "status": "Pass", + "evidence": "Base, adapter, and composed bundle have signatures, attestations, registry digests, and trusted checksums." + }, + { + "gate": "MSC-COMP-06", + "status": "Pass", + "evidence": "Signed manifest and reproducible merge job show immutable inputs, builder identity, and matched output digest." + }, + { + "gate": "MSC-COMP-07", + "status": "Pass", + "evidence": "Safety and regression evaluation ran on the exact composed deployed unit." + }, + { + "gate": "MSC-COMP-08", + "status": "Pass", + "evidence": "Deployment binds to the signed composition and includes canary monitoring, rollback bundle, and reconstruction test." + } + ], + "expected_assessment": { + "overall_status": "Pass", + "risk_rating": "Low", + "confidence": "High", + "finding": "No adapter composition provenance finding expected." + } +} diff --git a/skills/ai-security/model-supply-chain/tests/vulnerable/unbound_lora_adapter_hotload_without_composition_manifest.json b/skills/ai-security/model-supply-chain/tests/vulnerable/unbound_lora_adapter_hotload_without_composition_manifest.json new file mode 100644 index 00000000..984c17db --- /dev/null +++ b/skills/ai-security/model-supply-chain/tests/vulnerable/unbound_lora_adapter_hotload_without_composition_manifest.json @@ -0,0 +1,134 @@ +{ + "fixture": "unbound_lora_adapter_hotload_without_composition_manifest", + "skill": "model-supply-chain", + "description": "Vulnerable fixture for a signed LoRA adapter hot-loaded into an unpinned base model with missing tokenizer, merge, compatibility, safety, signed bundle, and rollback evidence.", + "deployment": { + "composition_id": "support-bot-prod-lora-latest", + "environment": "production", + "owner": "customer-support", + "serving_stack": "custom-transformers-service", + "promotion_ticket": "none" + }, + "observed_configuration": { + "base_model": { + "source": "public-registry/example-chat-base", + "revision": "latest", + "checksum": null, + "license": "unknown", + "model_card": "missing" + }, + "adapter": { + "name": "support-tone-lora", + "source": "public-registry/community/support-tone-lora", + "revision": "main", + "checksum": "checksum-copied-from-same-public-registry", + "license": "unknown", + "adapter_card": "missing", + "training_summary": "missing", + "signature": "adapter-file-signature-only" + }, + "loader": { + "code_path": "app/model_loader.py", + "behavior": "hot-loads ADAPTER_NAME from environment at service start", + "allowed_adapter_names": "*", + "composition_manifest": "missing", + "runtime_pin": "requirements allow compatible updates" + }, + "tokenizer_and_config": { + "tokenizer_revision": "not-recorded", + "special_tokens_revision": "not-recorded", + "chat_template_revision": "not-recorded", + "generation_config_revision": "not-recorded", + "framework_versions": "not-captured" + }, + "merge_and_runtime": { + "adapter_type": "LoRA", + "rank": "unknown", + "alpha": "unknown", + "target_modules": "unknown", + "quantization": "runtime default", + "merge_order": "implicit load order", + "merge_script": "not-versioned", + "tool_version": "not-recorded" + }, + "compatibility_constraints": { + "approved_pairs": "missing", + "disallowed_pairs": "missing", + "architecture_check": "missing", + "license_check": "missing" + } + }, + "trust_and_build_evidence": { + "base_model_signature": "missing", + "adapter_trust_root": "self-referential-public-registry-checksum", + "composition_signature": "missing", + "composition_bundle_digest": "missing", + "reproducible_merge_job": "missing", + "builder_identity": "unknown", + "immutable_inputs": false + }, + "safety_evaluation": { + "evaluated_artifact": "base-model-only", + "standard_benchmarks": "passed", + "domain_regression_suite": "missing", + "safety_probe_suite": "missing", + "jailbreak_regression": "missing", + "known_good_output_diff": "missing", + "matches_deployed_composition": false + }, + "deployment_controls": { + "deployment_binding": "service reads base and adapter names from environment", + "canary_plan": "missing", + "monitoring": "generic latency and error rate only", + "rollback_bundle": "base-model-latest", + "reconstruction_test": "not-possible" + }, + "expected_gate_results": [ + { + "gate": "MSC-COMP-01", + "status": "Fail", + "evidence": "No composition manifest; base is latest and adapter card, training summary, license, and trusted checksum are missing." + }, + { + "gate": "MSC-COMP-02", + "status": "Fail", + "evidence": "Tokenizer, special tokens, chat template, generation config, and framework versions are not pinned." + }, + { + "gate": "MSC-COMP-03", + "status": "Fail", + "evidence": "Rank, alpha, target modules, quantization, merge order, merge script, and tool version are undocumented." + }, + { + "gate": "MSC-COMP-04", + "status": "Fail", + "evidence": "No approved or disallowed compatibility pairs; any adapter can be hot-loaded by environment variable." + }, + { + "gate": "MSC-COMP-05", + "status": "Fail", + "evidence": "Adapter checksum is self-referential to the same public registry and base model has no signature." + }, + { + "gate": "MSC-COMP-06", + "status": "Fail", + "evidence": "No signed composed bundle, immutable input list, builder identity, or reproducible merge job." + }, + { + "gate": "MSC-COMP-07", + "status": "Fail", + "evidence": "Safety evidence covers standard benchmarks and the base model only, not the deployed composition." + }, + { + "gate": "MSC-COMP-08", + "status": "Not Evaluable", + "evidence": "Rollback points to a mutable base-model latest alias and the deployed composition cannot be reconstructed." + } + ], + "expected_assessment": { + "overall_status": "Fail", + "risk_rating": "High", + "confidence": "Low", + "finding": "Production adapter composition is not provenance-bound, safely evaluated, or rollback-ready." + } +}