From 4c405542a65d7bcb791b064fa379d25e7c2f5eb1 Mon Sep 17 00:00:00 2001 From: Omar Santos Date: Sun, 12 Apr 2026 19:59:50 -0400 Subject: [PATCH 1/4] Reorganize additional skills directory and add memory-safe migration skill Move OWASP reference skills from sources/owasp/ to sources/additional-skills/owasp/ to establish a cleaner directory structure for additional skills. Add the new memory-safe language migration skill under sources/additional-skills/memory-safe-migration/ with SKILL.md, reference documents (language selection, FFI security, migration patterns, assessment checklist), and a static analysis assessment script. Closes #46, closes #47, closes #48 Made-with: Cursor --- .../memory-safe-migration/SKILL.md | 131 ++++++ .../references/assessment-checklist.md | 93 +++++ .../references/ffi-security.md | 196 +++++++++ .../references/language-selection.md | 66 +++ .../references/migration-patterns.md | 306 ++++++++++++++ .../scripts/assess-migration.py | 386 ++++++++++++++++++ .../owasp/codeguard-0-ajax-security.md | 0 .../codeguard-0-attack-surface-analysis.md | 0 .../owasp/codeguard-0-authentication.md | 0 ...uard-0-authorization-testing-automation.md | 0 .../owasp/codeguard-0-authorization.md | 0 .../owasp/codeguard-0-bean-validation.md | 0 ...ard-0-browser-extension-vulnerabilities.md | 0 ...codeguard-0-c-based-toolchain-hardening.md | 0 ...0-choosing-and-using-security-questions.md | 0 .../owasp/codeguard-0-ci-cd-security.md | 0 .../owasp/codeguard-0-clickjacking-defense.md | 0 .../codeguard-0-content-security-policy.md | 0 .../codeguard-0-cookie-theft-mitigation.md | 0 ...eguard-0-credential-stuffing-prevention.md | 0 ...0-cross-site-request-forgery-prevention.md | 0 ...guard-0-cross-site-scripting-prevention.md | 0 .../codeguard-0-cryptographic-storage.md | 0 ...-0-cw-cryptographic-security-guidelines.md | 0 ...ard-0-cw-memory-string-usage-guidelines.md | 0 .../owasp/codeguard-0-database-security.md | 0 .../owasp/codeguard-0-deserialization.md | 0 .../codeguard-0-django-rest-framework.md | 0 .../owasp/codeguard-0-django-security.md | 0 .../owasp/codeguard-0-docker-security.md | 0 .../codeguard-0-dom-based-xss-prevention.md | 0 .../codeguard-0-dom-clobbering-prevention.md | 0 .../owasp/codeguard-0-dotnet-security.md | 0 .../owasp/codeguard-0-error-handling.md | 0 .../owasp/codeguard-0-file-upload.md | 0 .../owasp/codeguard-0-forgot-password.md | 0 .../owasp/codeguard-0-graphql.md | 0 .../owasp/codeguard-0-html5-security.md | 0 .../owasp/codeguard-0-http-headers.md | 0 ...eguard-0-http-strict-transport-security.md | 0 .../owasp/codeguard-0-injection-prevention.md | 0 .../owasp/codeguard-0-input-validation.md | 0 ...cure-direct-object-reference-prevention.md | 0 .../owasp/codeguard-0-jaas.md | 0 .../owasp/codeguard-0-java-security.md | 0 .../codeguard-0-json-web-token-for-java.md | 0 .../owasp/codeguard-0-key-management.md | 0 .../owasp/codeguard-0-kubernetes-security.md | 0 .../owasp/codeguard-0-laravel.md | 0 .../codeguard-0-ldap-injection-prevention.md | 0 ...deguard-0-legacy-application-management.md | 0 .../owasp/codeguard-0-logging-vocabulary.md | 0 .../owasp/codeguard-0-mass-assignment.md | 0 .../codeguard-0-microservices-security.md | 0 ...codeguard-0-mobile-application-security.md | 0 .../codeguard-0-multifactor-authentication.md | 0 .../owasp/codeguard-0-network-segmentation.md | 0 .../owasp/codeguard-0-nodejs-docker.md | 0 .../owasp/codeguard-0-nodejs-security.md | 0 .../owasp/codeguard-0-npm-security.md | 0 .../owasp/codeguard-0-oauth2.md | 0 .../owasp/codeguard-0-open-redirect.md | 0 ...odeguard-0-os-command-injection-defense.md | 0 .../owasp/codeguard-0-password-storage.md | 0 .../owasp/codeguard-0-php-configuration.md | 0 .../owasp/codeguard-0-pinning.md | 0 ...eguard-0-prototype-pollution-prevention.md | 0 .../codeguard-0-query-parameterization.md | 0 .../owasp/codeguard-0-rest-assessment.md | 0 .../owasp/codeguard-0-rest-security.md | 0 .../owasp/codeguard-0-ruby-on-rails.md | 0 .../owasp/codeguard-0-safe-c-functions.md | 0 .../owasp/codeguard-0-saml-security.md | 0 ...guard-0-securing-cascading-style-sheets.md | 0 ...-server-side-request-forgery-prevention.md | 0 .../owasp/codeguard-0-session-management.md | 0 .../codeguard-0-sql-injection-prevention.md | 0 .../owasp/codeguard-0-symfony.md | 0 ...ard-0-third-party-javascript-management.md | 0 .../owasp/codeguard-0-threat-modeling.md | 0 .../codeguard-0-transaction-authorization.md | 0 .../codeguard-0-transport-layer-security.md | 0 ...rd-0-unvalidated-redirects-and-forwards.md | 0 .../codeguard-0-user-privacy-protection.md | 0 .../owasp/codeguard-0-virtual-patching.md | 0 ...uard-0-vulnerable-dependency-management.md | 0 .../owasp/codeguard-0-web-service-security.md | 0 ...eguard-0-xml-external-entity-prevention.md | 0 .../owasp/codeguard-0-xml-security.md | 0 .../owasp/codeguard-0-xs-leaks.md | 0 .../owasp/codeguard-0-xss-filter-evasion.md | 0 .../codeguard-0-zero-trust-architecture.md | 0 92 files changed, 1178 insertions(+) create mode 100644 sources/additional-skills/memory-safe-migration/SKILL.md create mode 100644 sources/additional-skills/memory-safe-migration/references/assessment-checklist.md create mode 100644 sources/additional-skills/memory-safe-migration/references/ffi-security.md create mode 100644 sources/additional-skills/memory-safe-migration/references/language-selection.md create mode 100644 sources/additional-skills/memory-safe-migration/references/migration-patterns.md create mode 100644 sources/additional-skills/memory-safe-migration/scripts/assess-migration.py rename sources/{ => additional-skills}/owasp/codeguard-0-ajax-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-attack-surface-analysis.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-authentication.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-authorization-testing-automation.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-authorization.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-bean-validation.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-browser-extension-vulnerabilities.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-c-based-toolchain-hardening.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-choosing-and-using-security-questions.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-ci-cd-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-clickjacking-defense.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-content-security-policy.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-cookie-theft-mitigation.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-credential-stuffing-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-cross-site-request-forgery-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-cross-site-scripting-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-cryptographic-storage.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-cw-cryptographic-security-guidelines.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-cw-memory-string-usage-guidelines.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-database-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-deserialization.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-django-rest-framework.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-django-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-docker-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-dom-based-xss-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-dom-clobbering-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-dotnet-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-error-handling.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-file-upload.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-forgot-password.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-graphql.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-html5-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-http-headers.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-http-strict-transport-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-injection-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-input-validation.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-insecure-direct-object-reference-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-jaas.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-java-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-json-web-token-for-java.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-key-management.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-kubernetes-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-laravel.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-ldap-injection-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-legacy-application-management.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-logging-vocabulary.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-mass-assignment.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-microservices-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-mobile-application-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-multifactor-authentication.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-network-segmentation.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-nodejs-docker.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-nodejs-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-npm-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-oauth2.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-open-redirect.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-os-command-injection-defense.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-password-storage.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-php-configuration.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-pinning.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-prototype-pollution-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-query-parameterization.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-rest-assessment.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-rest-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-ruby-on-rails.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-safe-c-functions.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-saml-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-securing-cascading-style-sheets.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-server-side-request-forgery-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-session-management.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-sql-injection-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-symfony.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-third-party-javascript-management.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-threat-modeling.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-transaction-authorization.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-transport-layer-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-unvalidated-redirects-and-forwards.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-user-privacy-protection.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-virtual-patching.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-vulnerable-dependency-management.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-web-service-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-xml-external-entity-prevention.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-xml-security.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-xs-leaks.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-xss-filter-evasion.md (100%) rename sources/{ => additional-skills}/owasp/codeguard-0-zero-trust-architecture.md (100%) diff --git a/sources/additional-skills/memory-safe-migration/SKILL.md b/sources/additional-skills/memory-safe-migration/SKILL.md new file mode 100644 index 0000000..6ac6aea --- /dev/null +++ b/sources/additional-skills/memory-safe-migration/SKILL.md @@ -0,0 +1,131 @@ +--- +name: memory-safe-migration +description: >- + Guide secure migration of code from memory-unsafe languages (C, C++, Assembly) + to memory-safe languages (Rust, Go, Java, C#, Swift). Use when migrating or + rewriting legacy C/C++ code, designing FFI boundaries between safe and unsafe + code, writing new modules in existing C/C++ codebases, reviewing mixed-language + projects, planning memory safety roadmaps, or when an AI agent is about to + generate new C/C++ code that could be written in a memory-safe language instead. + Also triggers on CISA/NSA memory safety compliance discussions. +license: CC-BY-4.0 +metadata: + author: codeguard-community + version: "1.0" + category: memory-safety +--- + +# Memory-safe language migration + +## When this skill activates + +- User asks to migrate, port, or rewrite C/C++ code to Rust, Go, Java, C#, or Swift +- User asks to add a new module or feature to an existing C/C++ project +- User asks to design an FFI boundary between safe and unsafe code +- User asks about memory safety roadmaps or CISA/NSA compliance +- User asks to review mixed-language code for safety issues +- AI agent is about to generate new C/C++ code — check if an MSL alternative is viable + +## Decision: new code language selection + +Before writing any new code, ask: + +1. Is there an explicit constraint requiring C/C++? (bare-metal with no MSL runtime, + hard real-time below 1μs, existing codebase policy) +2. If no constraint exists, default to a memory-safe language +3. Select the target language using the guide in [references/language-selection.md](references/language-selection.md) + +If the project is predominantly C/C++, write the new module in an MSL and integrate +via FFI. See [references/ffi-security.md](references/ffi-security.md) for boundary rules. + +## Migration workflow + +Follow these steps for every migration task: + +### Step 1: Assess the component + +Run the assessment script to evaluate migration priority and feasibility: + +```bash +python scripts/assess-migration.py --file +``` + +Or manually evaluate using the checklist in [references/assessment-checklist.md](references/assessment-checklist.md). + +Priority order for migration: +1. Network-facing code (parsers, protocol handlers, TLS) +2. Code handling untrusted input (file parsers, deserialization) +3. Cryptographic implementations +4. Privilege boundary code (auth enforcement) +5. Code with a history of memory-related CVEs +6. Internal utility code + +### Step 2: Write tests first + +Never migrate a component without test coverage. If no tests exist, write them +against the C/C++ implementation before touching anything. These tests become the +correctness oracle for the new implementation. + +### Step 3: Migrate incrementally + +One function or module at a time. Never rewrite an entire codebase in one pass. +Follow the Android model: new code in MSL, existing stable code stays in place, +proportion of unsafe code decreases over time. + +For common migration patterns (buffers, strings, concurrency, error handling), +see [references/migration-patterns.md](references/migration-patterns.md). + +### Step 4: Secure the FFI boundary + +Every interface between safe and unsafe code is a security boundary. Follow all +rules in [references/ffi-security.md](references/ffi-security.md). Key rules: + +- Validate all inputs from the unsafe side (null checks, bounds checks, type checks) +- Minimize `unsafe` blocks — wrap only the minimum necessary operation +- Document every `unsafe` block with a `// SAFETY:` comment +- The allocator that created memory must free it — never mix allocators +- Never panic across FFI boundaries +- Catch all panics with `std::panic::catch_unwind` at FFI entry points + +### Step 5: Validate + +After every migration unit, verify: + +- All existing tests pass against the new implementation +- No new `unsafe` surface without documented safety invariants +- FFI boundaries fuzzed with malformed inputs, null pointers, extreme lengths +- Memory safety tools run clean (Miri for Rust, race detector for Go, ASan for C side) +- Performance benchmarked against the original — no unacceptable regression +- All new MSL dependencies audited for trustworthiness and active maintenance + +### Step 6: Update build and CI + +- Integrate the MSL toolchain (cargo, go build, etc.) into the existing build system +- Add MSL-specific linting (`clippy` for Rust, `go vet` for Go) +- Add formatting checks (`rustfmt`, `gofmt`) +- Add safety-specific CI checks (deny `unsafe` without annotation, dependency audit) + +## Anti-patterns to prevent + +Never do these during migration: + +- **Wrapping unsafe C in a "safe" API without actual safety guarantees** — if the + wrapper just passes through without validation, it provides false confidence +- **Using `unsafe` to replicate C-style patterns in Rust** — if extensive `unsafe` + is needed, the approach should be redesigned or the code should remain in C +- **Migrating without tests** — write tests for C/C++ first, then validate MSL version +- **Ignoring error handling differences** — C uses return codes, Rust uses `Result`, + Go uses multiple returns. Every error path must be explicitly mapped +- **Assuming GC languages need no resource discipline** — they prevent memory corruption + but can still leak file handles, sockets, and connections. Use `defer`, `try-with-resources`, + `using`, or `with` patterns +- **Migrating performance-critical loops without benchmarking** — verify first + +## References + +For detailed guidance on specific topics: + +- [Language selection guide](references/language-selection.md) +- [FFI boundary security rules](references/ffi-security.md) +- [Common migration patterns](references/migration-patterns.md) +- [Assessment checklist](references/assessment-checklist.md) diff --git a/sources/additional-skills/memory-safe-migration/references/assessment-checklist.md b/sources/additional-skills/memory-safe-migration/references/assessment-checklist.md new file mode 100644 index 0000000..195c4ca --- /dev/null +++ b/sources/additional-skills/memory-safe-migration/references/assessment-checklist.md @@ -0,0 +1,93 @@ +# Migration assessment checklist + +Use this checklist to evaluate whether a component should be migrated, its priority, +and its feasibility. Score each category and sum for an overall migration priority. + +## Priority scoring + +### Vulnerability history (0-10 points) + +- [ ] Count of memory-related CVEs in this component + - 0 CVEs: 0 points + - 1-2 CVEs: 3 points + - 3-5 CVEs: 6 points + - 6+ CVEs: 10 points +- [ ] Severity of past CVEs + - Mostly Low/Medium: no adjustment + - Any Critical/High: +2 points +- [ ] Recurrence of same bug class (e.g., buffer overflow fixed, then reappears): +2 points + +### Exposure surface (0-10 points) + +- [ ] Network-facing (accepts data from network): +4 points +- [ ] Processes untrusted input (user files, external APIs, deserialization): +3 points +- [ ] Handles cryptographic material (keys, certificates, random): +2 points +- [ ] Runs with elevated privileges (root, SYSTEM, kernel): +2 points +- [ ] Internal-only utility with no external input: 0 points + +### Risk acceleration from AI (0-5 points) + +- [ ] Component uses patterns known to be easily discoverable by AI fuzzing + (simple parsers, flat buffer handling): +3 points +- [ ] Component has limited exploit mitigations (no ASLR, no stack canaries, + no CFI): +2 points + +### Total priority score + +- **20+**: Critical — migrate immediately +- **15-19**: High — schedule for next development cycle +- **8-14**: Medium — plan for migration within the roadmap period +- **0-7**: Low — migrate opportunistically or when component is modified + +## Feasibility evaluation + +### Blockers (any of these may prevent migration) + +- [ ] Inline assembly that cannot be replaced +- [ ] Hard real-time constraint below 1μs with no MSL equivalent +- [ ] Platform with no MSL compiler support (rare embedded targets) +- [ ] Regulatory or certification requirement mandating specific language +- [ ] Component is scheduled for end-of-life/replacement — migration not worthwhile + +### Complexity factors (affect timeline, not feasibility) + +- [ ] Lines of code in the component + - Under 1,000: Small — days to weeks + - 1,000-10,000: Medium — weeks to months + - 10,000-100,000: Large — months; consider incremental approach + - 100,000+: Very large — must be incremental; full rewrite not recommended +- [ ] Number of external C/C++ library dependencies + - Each dependency must have an MSL equivalent or be accessed via FFI + - List each dependency and its MSL alternative status +- [ ] Platform-specific system call usage + - List syscalls used; verify MSL support on all target platforms +- [ ] Existing test coverage + - No tests: must write tests first (add to timeline) + - Partial coverage: identify untested paths + - Comprehensive coverage: ready for migration validation + +### Team readiness + +- [ ] Team expertise in target MSL + - Expert: no training needed + - Intermediate: minor ramp-up + - Beginner: budget training time (2-4 weeks for productive Rust, 1-2 weeks for Go) + - None: consider hiring or partnering; do not migrate without expertise +- [ ] Availability of code review expertise in target MSL +- [ ] CI/CD pipeline supports target MSL toolchain + +## Output + +After assessment, produce a migration recommendation: + +``` +Component: [name] +Priority score: [X] / 25 +Feasibility: [Go / Go with caveats / Blocked] +Blockers: [list any] +Recommended target language: [language + rationale] +Estimated effort: [T-shirt size + calendar estimate] +Dependencies requiring FFI: [list] +Test coverage gap: [description] +Recommended migration order: [which functions/modules first] +``` diff --git a/sources/additional-skills/memory-safe-migration/references/ffi-security.md b/sources/additional-skills/memory-safe-migration/references/ffi-security.md new file mode 100644 index 0000000..ad69da5 --- /dev/null +++ b/sources/additional-skills/memory-safe-migration/references/ffi-security.md @@ -0,0 +1,196 @@ +# FFI boundary security rules + +The interface between memory-safe and memory-unsafe code is a critical attack surface. +Treat every FFI boundary with the same rigor as a network API at a trust boundary. + +## Mandatory rules + +### 1. Validate all inputs from the unsafe side + +Every pointer, length, and value crossing from C/C++ into MSL code must be validated +before use. + +```rust +// CORRECT: Full validation at the boundary +#[no_mangle] +pub extern "C" fn process_buffer(ptr: *const u8, len: usize) -> i32 { + // Validate pointer is not null + if ptr.is_null() { + return -1; + } + + // Validate length is reasonable + if len == 0 || len > MAX_BUFFER_SIZE { + return -2; + } + + // Create a safe slice — this is the trust boundary crossing + let data = unsafe { std::slice::from_raw_parts(ptr, len) }; + + // From here on, all code is fully safe Rust + match process_data_safely(data) { + Ok(result) => result, + Err(_) => -3, + } +} +``` + +```go +// CORRECT: Go validation of C pointer via cgo +/* +#include +*/ +import "C" +import "unsafe" + +func ProcessBuffer(ptr *C.char, length C.int) C.int { + if ptr == nil { + return -1 + } + if length <= 0 || length > maxBufferSize { + return -2 + } + + // Convert to Go slice safely + data := C.GoBytes(unsafe.Pointer(ptr), length) + // data is now a Go-managed copy — safe to use + return C.int(processDataSafely(data)) +} +``` + +```java +// CORRECT: Java validation via Panama FFI (Java 22+) +public static int processBuffer(MemorySegment segment) { + if (segment.equals(MemorySegment.NULL)) { + return -1; + } + long size = segment.byteSize(); + if (size == 0 || size > MAX_BUFFER_SIZE) { + return -2; + } + + // Copy into a managed byte array + byte[] data = segment.toArray(ValueLayout.JAVA_BYTE); + return processDataSafely(data); +} +``` + +### 2. Minimize unsafe surface area + +All `unsafe` blocks (Rust) or unsafe operations must be: + +- **As small as possible** — wrap only the minimum necessary operation +- **Documented** with a `// SAFETY:` comment explaining the invariant being upheld +- **Isolated** behind safe abstractions that enforce the invariant via their API +- **Never used to silence the borrow checker** or bypass ownership rules + +```rust +// CORRECT: Minimal, documented unsafe +fn read_c_string(ptr: *const c_char) -> Result { + if ptr.is_null() { + return Err(Error::NullPointer); + } + // SAFETY: We verified ptr is non-null. The caller guarantees it points + // to a valid, null-terminated C string for the duration of this call. + let c_str = unsafe { CStr::from_ptr(ptr) }; + c_str.to_str().map(|s| s.to_owned()).map_err(|_| Error::InvalidUtf8) +} + +// WRONG: Large unsafe block doing everything +unsafe fn read_c_string_bad(ptr: *const c_char) -> String { + CStr::from_ptr(ptr).to_str().unwrap().to_owned() + // No null check, no error handling, entire function is unsafe +} +``` + +### 3. Memory ownership across FFI + +The allocator that created the memory must free it. Never mix allocators. + +```rust +// Correct pattern: Rust allocates, Rust provides the free function +#[no_mangle] +pub extern "C" fn create_resource() -> *mut Resource { + Box::into_raw(Box::new(Resource::new())) +} + +#[no_mangle] +pub extern "C" fn destroy_resource(ptr: *mut Resource) { + if !ptr.is_null() { + // SAFETY: ptr was created by create_resource via Box::into_raw. + // Caller guarantees this is only called once per resource. + unsafe { drop(Box::from_raw(ptr)) }; + } +} +``` + +Rules: +- Document who owns every pointer in every FFI function signature +- Provide paired allocate/free functions when exposing MSL allocations to C +- Never `free()` Rust-allocated memory or `drop()` C-allocated memory +- In Go, remember that cgo-allocated memory follows C rules, not Go GC rules + +### 4. Error handling across FFI + +- **Never panic across FFI boundaries** — this is undefined behavior in Rust +- Use C-compatible error codes or out-parameters +- Catch all panics at the FFI boundary + +```rust +#[no_mangle] +pub extern "C" fn safe_entry_point(input: *const u8, len: usize) -> i32 { + let result = std::panic::catch_unwind(|| { + internal_logic(input, len) + }); + + match result { + Ok(Ok(value)) => value, + Ok(Err(_)) => -1, // Application error + Err(_) => -99, // Panic caught — return error, do not propagate + } +} +``` + +In Go, panics do not cross cgo boundaries by default, but exported functions +should still use `defer/recover` for robustness: + +```go +//export SafeEntryPoint +func SafeEntryPoint(input *C.char, length C.int) C.int { + defer func() { + if r := recover(); r != nil { + // Log the panic, return error code + } + }() + // ... processing logic + return C.int(result) +} +``` + +### 5. Thread safety across FFI + +- Document thread safety guarantees for every FFI function +- If the C side uses global state, protect it with synchronization on the MSL side +- In Rust, FFI functions are `unsafe` by default because the compiler cannot + verify thread safety across language boundaries — the developer must ensure it +- In Go, be aware that goroutines may call C code concurrently; the C code must + be thread-safe or protected by a mutex on the Go side + +### 6. String encoding across FFI + +- C strings are null-terminated byte sequences with no encoding guarantee +- Rust strings are UTF-8, Go strings are UTF-8, Java strings are UTF-16 internally +- Always validate encoding at the boundary +- Never assume a C string is valid UTF-8 — use fallible conversion functions + (`CStr::to_str()` in Rust, explicit encoding conversion in Go/Java) + +## Testing FFI boundaries + +Every FFI boundary must be tested with: + +1. **Null pointers** for every pointer parameter +2. **Zero-length** and **maximum-length** buffers +3. **Malformed input** (invalid UTF-8, truncated data, embedded nulls) +4. **Concurrent access** from multiple threads +5. **Fuzz testing** with tools like `cargo-fuzz`, `go-fuzz`, or AFL +6. **Memory sanitizers** (Miri for Rust, ASan/MSan for C side, race detector for Go) diff --git a/sources/additional-skills/memory-safe-migration/references/language-selection.md b/sources/additional-skills/memory-safe-migration/references/language-selection.md new file mode 100644 index 0000000..af074fe --- /dev/null +++ b/sources/additional-skills/memory-safe-migration/references/language-selection.md @@ -0,0 +1,66 @@ +# Language selection guide + +Choose the target memory-safe language based on the use case, performance requirements, +and ecosystem constraints. + +## Decision matrix + +| Use case | Recommended MSL | Rationale | +|---|---|---| +| Systems programming, OS, embedded, drivers | Rust | Zero-cost abstractions, no GC, C-level performance, ownership model prevents data races at compile time | +| Network services, microservices, CLI tools | Rust or Go | Both excel; Go for simpler concurrency model and faster compilation, Rust for tighter memory control | +| Enterprise applications, web backends | Java, C#, Go | Mature ecosystems, strong library support, GC handles memory automatically | +| iOS / macOS applications | Swift | Native platform support, ARC memory management, Apple ecosystem integration | +| Android applications | Kotlin, Java | Native Android support, full memory safety, mature tooling | +| Scripting, automation, data processing | Python | Rapid development, extensive libraries; use Rust/C FFI for performance-critical paths | +| Real-time systems with GC constraints | Rust | No garbage collector pauses; deterministic memory management via ownership | +| WebAssembly targets | Rust or Go | Both compile to WASM; Rust produces smaller binaries | + +## Key factors in selection + +### Performance requirements + +- **Latency-sensitive / real-time**: Rust (no GC pauses) +- **Throughput-focused with simpler code**: Go (GC is fast, goroutines are lightweight) +- **Acceptable GC pauses**: Java, C#, Go, Kotlin + +### Team expertise + +- If the team knows Go well and the use case fits, use Go — a migration in a language + the team understands beats a theoretically superior choice they cannot maintain +- Factor in hiring: Rust expertise is growing but still less common than Go or Java +- Consider training investment vs. migration urgency + +### Ecosystem and library availability + +- Check that equivalent libraries exist in the target MSL before committing +- Critical dependencies (TLS, crypto, protocol parsers) must have mature, audited + implementations in the target language +- If a required library only exists in C, the FFI boundary cost may outweigh benefits + for that specific component + +### Interoperability with existing code + +- **Rust ↔ C**: Excellent. `#[no_mangle]`, `extern "C"`, `bindgen`, `cbindgen` tooling is mature +- **Go ↔ C**: Good via cgo, but cgo has performance overhead and complicates cross-compilation +- **Java ↔ C**: Via JNI or Panama FFI (Java 22+); JNI is verbose but well-understood +- **Swift ↔ C**: Direct C interop built into the language +- **C# ↔ C**: Via P/Invoke or LibraryImport; well-supported on .NET + +## Memory-safe languages recognized by CISA/NSA + +The following languages are recognized as memory-safe in CISA/NSA guidance: + +- Ada +- C# +- Delphi / Object Pascal +- Go +- Java +- Python +- Ruby +- Rust +- Swift + +Note: C and C++ are explicitly classified as memory-unsafe. Assembly language is also +memory-unsafe. Using C++ with smart pointers and static analysis reduces risk but does +not achieve the memory safety guarantees provided by MSLs. diff --git a/sources/additional-skills/memory-safe-migration/references/migration-patterns.md b/sources/additional-skills/memory-safe-migration/references/migration-patterns.md new file mode 100644 index 0000000..65ccdfb --- /dev/null +++ b/sources/additional-skills/memory-safe-migration/references/migration-patterns.md @@ -0,0 +1,306 @@ +# Common migration patterns + +Side-by-side patterns for migrating C/C++ code to memory-safe languages. + +## Buffer operations + +### C (vulnerable) +```c +void process(const char *input, size_t len) { + char buffer[256]; + memcpy(buffer, input, len); // Buffer overflow if len > 256 +} +``` + +### Rust (safe) +```rust +fn process(input: &[u8]) { + let mut buffer = vec![0u8; input.len()]; + buffer.copy_from_slice(input); // Panics if sizes mismatch — no silent overflow +} +``` + +### Go (safe) +```go +func process(input []byte) { + buffer := make([]byte, len(input)) + copy(buffer, input) // copy is bounds-safe, copies min(dst, src) bytes +} +``` + +## String handling + +### C (vulnerable) +```c +char *concat(const char *a, const char *b) { + char *result = malloc(strlen(a) + strlen(b) + 1); + if (!result) return NULL; // Often forgotten + strcpy(result, a); // Unsafe + strcat(result, b); // Unsafe + return result; // Caller must free — easy to forget +} +``` + +### Rust (safe) +```rust +fn concat(a: &str, b: &str) -> String { + format!("{}{}", a, b) // Allocation, sizing, UTF-8 all handled +} +``` + +### Go (safe) +```go +func concat(a, b string) string { + return a + b // Strings are immutable, concatenation allocates safely +} +// For many concatenations, use strings.Builder for performance +``` + +### Java (safe) +```java +String concat(String a, String b) { + return a + b; // Or use StringBuilder for loops +} +``` + +## Linked data structures + +### C (vulnerable) +```c +struct Node { + int value; + struct Node *next; // Raw pointer — no ownership semantics +}; +// Manual insert, delete, traversal — use-after-free, dangling pointers +``` + +### Rust (safe) +```rust +// Option 1: Ownership-based +enum List { + Cons(i32, Box), + Nil, +} + +// Option 2: Just use the standard library +use std::collections::VecDeque; // or LinkedList, but VecDeque is usually better +``` + +### Go (safe) +```go +// Use the standard library +import "container/list" + +l := list.New() +l.PushBack(42) +// GC handles all memory — no dangling pointers possible +``` + +## Concurrency + +### C (vulnerable — data race) +```c +static int counter = 0; +// Multiple threads increment — undefined behavior without locks +void increment() { counter++; } +``` + +### Rust (safe — compiler-enforced) +```rust +use std::sync::atomic::{AtomicI32, Ordering}; + +static COUNTER: AtomicI32 = AtomicI32::new(0); + +fn increment() { + COUNTER.fetch_add(1, Ordering::SeqCst); +} + +// For complex shared state, Mutex prevents access without holding the lock: +use std::sync::Mutex; +let shared = Mutex::new(vec![1, 2, 3]); +{ + let mut data = shared.lock().unwrap(); + data.push(4); // Lock held — safe +} // Lock automatically released +``` + +### Go (safe — channels or sync) +```go +import "sync/atomic" + +var counter int64 + +func increment() { + atomic.AddInt64(&counter, 1) +} + +// Or use channels for CSP-style concurrency: +ch := make(chan int, 100) +go func() { ch <- computeResult() }() +result := <-ch +``` + +## Error handling + +### C (easy to ignore) +```c +int result = do_something(); +// Return code often unchecked — errors silently ignored +``` + +### Rust (compiler-enforced) +```rust +// Result must be handled — compiler warns on unused Result +fn do_something() -> Result { + let data = read_file()?; // ? propagates errors automatically + let parsed = parse(data)?; + Ok(parsed) +} +``` + +### Go (explicit but not enforced) +```go +result, err := doSomething() +if err != nil { + return fmt.Errorf("do_something failed: %w", err) // Wrap for context +} +// Use result +``` + +## File and resource handling + +### C (easy to leak) +```c +FILE *f = fopen("data.txt", "r"); +// ... processing that might return early or throw +// fclose(f) might never execute — resource leak +``` + +### Rust (automatic via RAII) +```rust +// File is automatically closed when it goes out of scope +let contents = std::fs::read_to_string("data.txt")?; +// Or for explicit control: +{ + let file = File::open("data.txt")?; + // use file +} // file.drop() called automatically — fd closed +``` + +### Go (defer pattern) +```go +f, err := os.Open("data.txt") +if err != nil { + return err +} +defer f.Close() // Guaranteed to run when function returns +// ... processing +``` + +### Java (try-with-resources) +```java +try (var reader = new BufferedReader(new FileReader("data.txt"))) { + // use reader +} // Automatically closed, even on exception +``` + +## Array/slice operations + +### C (no bounds checking) +```c +int arr[10]; +arr[15] = 42; // Buffer overflow — undefined behavior, no error +``` + +### Rust (bounds checked) +```rust +let mut arr = [0i32; 10]; +arr[15] = 42; // Panics at runtime with clear error message +// Or use .get() for recoverable bounds checking: +if let Some(val) = arr.get_mut(15) { + *val = 42; +} else { + // Handle out-of-bounds gracefully +} +``` + +### Go (bounds checked) +```go +arr := make([]int, 10) +arr[15] = 42 // Panics with "index out of range [15] with length 10" +``` + +## Dynamic memory + +### C (manual, error-prone) +```c +int *data = malloc(n * sizeof(int)); +if (!data) { /* handle OOM — often forgotten */ } +// ... use data +// Must remember to free at every exit path +free(data); +// data is now a dangling pointer if accessed again +``` + +### Rust (ownership system) +```rust +let data: Vec = vec![0; n]; // Allocation checked, zeroed +// ... use data +// Automatically freed when data goes out of scope +// Compiler prevents use-after-free at compile time +``` + +### Go (garbage collected) +```go +data := make([]int, n) // Allocation and zeroing handled +// ... use data +// GC frees when no references remain — no dangling pointers possible +``` + +## Network server pattern + +### C (manual socket management) +```c +int server_fd = socket(AF_INET, SOCK_STREAM, 0); +struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(8080) }; +bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)); +listen(server_fd, SOMAXCONN); +// Manual accept loop, buffer management, error handling for every syscall +// Thread management, connection cleanup — hundreds of lines +``` + +### Rust (safe, async) +```rust +use tokio::net::TcpListener; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let listener = TcpListener::bind("0.0.0.0:8080").await?; + loop { + let (socket, _) = listener.accept().await?; + tokio::spawn(async move { + handle_connection(socket).await; + }); + } +} +``` + +### Go (safe, concurrent) +```go +func main() { + listener, err := net.Listen("tcp", ":8080") + if err != nil { + log.Fatal(err) + } + defer listener.Close() + + for { + conn, err := listener.Accept() + if err != nil { + log.Println(err) + continue + } + go handleConnection(conn) // Safe concurrent handling + } +} +``` diff --git a/sources/additional-skills/memory-safe-migration/scripts/assess-migration.py b/sources/additional-skills/memory-safe-migration/scripts/assess-migration.py new file mode 100644 index 0000000..b9dd988 --- /dev/null +++ b/sources/additional-skills/memory-safe-migration/scripts/assess-migration.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 +""" +Memory-safe migration assessment tool. + +Analyzes C/C++ source files to estimate migration priority based on: +- Unsafe function usage (strcpy, sprintf, malloc, free, etc.) +- Buffer declaration patterns +- Pointer arithmetic +- Network/file I/O exposure +- Concurrency patterns + +Usage: + python assess-migration.py --file + python assess-migration.py --file src/ --format json + +Output: + A migration priority report for each file with risk indicators, + an overall priority score, and recommended migration order. +""" + +import argparse +import os +import re +import json +import sys +from pathlib import Path +from dataclasses import dataclass, field, asdict +from typing import List, Dict, Tuple + +# ── Risk patterns ────────────────────────────────────────────────────────── + +# Unsafe C functions that should be replaced +UNSAFE_FUNCTIONS = { + # String functions — buffer overflow risk + "strcpy": ("Buffer overflow — no bounds checking", 3), + "strcat": ("Buffer overflow — no bounds checking", 3), + "sprintf": ("Buffer overflow — no bounds checking", 3), + "gets": ("Buffer overflow — always unsafe, removed in C11", 5), + "scanf": ("Buffer overflow with %s format", 2), + "vsprintf": ("Buffer overflow — no bounds checking", 3), + + # Memory functions — overflow or misuse risk + "memcpy": ("Potential buffer overflow if size unchecked", 2), + "memmove": ("Potential buffer overflow if size unchecked", 2), + "memset": ("Potential buffer overflow if size unchecked", 1), + + # Memory management — use-after-free, double-free, leak risk + "malloc": ("Manual memory management — leak/use-after-free risk", 2), + "calloc": ("Manual memory management — leak/use-after-free risk", 2), + "realloc": ("Manual memory management — complex ownership", 3), + "free": ("Manual memory management — double-free risk", 2), + "alloca": ("Stack allocation — stack overflow risk", 3), + + # Format string vulnerabilities + "printf": ("Format string vulnerability if user-controlled", 1), + "fprintf": ("Format string vulnerability if user-controlled", 1), + "syslog": ("Format string vulnerability if user-controlled", 2), + + # Other unsafe patterns + "system": ("Command injection risk", 4), + "popen": ("Command injection risk", 4), + "exec": ("Command injection risk", 3), + "atoi": ("No error checking on conversion", 1), + "atol": ("No error checking on conversion", 1), + "atof": ("No error checking on conversion", 1), +} + +# Patterns indicating network exposure +NETWORK_PATTERNS = [ + (r"\bsocket\s*\(", "Socket creation — network-facing code"), + (r"\bbind\s*\(", "Socket binding — server code"), + (r"\blisten\s*\(", "Socket listening — server code"), + (r"\baccept\s*\(", "Socket accept — server code"), + (r"\brecv\s*\(", "Network receive — processes untrusted data"), + (r"\brecvfrom\s*\(", "Network receive — processes untrusted data"), + (r"\brecvmsg\s*\(", "Network receive — processes untrusted data"), + (r"\bSSL_read\s*\(", "TLS data handling"), + (r"\bSSL_write\s*\(", "TLS data handling"), + (r"#include\s*<.*?(netinet|arpa|sys/socket|netdb)", "Network headers included"), +] + +# Patterns indicating file/input processing +INPUT_PATTERNS = [ + (r"\bfopen\s*\(", "File I/O — may process untrusted files"), + (r"\bfread\s*\(", "File read — may process untrusted data"), + (r"\bopen\s*\(", "File descriptor open"), + (r"\bread\s*\(", "Low-level read — may process untrusted data"), + (r"\bfscanf\s*\(", "Formatted file input"), + (r"\bxml|json|yaml|parse", "Data parsing — untrusted input risk"), +] + +# Patterns indicating concurrency +CONCURRENCY_PATTERNS = [ + (r"\bpthread_", "POSIX threads — data race risk"), + (r"\bstd::thread", "C++ threads — data race risk"), + (r"\bstd::mutex", "Mutex usage — potential deadlock"), + (r"\bstd::atomic", "Atomic operations — complex ordering"), + (r"\bvolatile\b", "Volatile — often misused for synchronization"), + (r"\bfork\s*\(", "Process forking — shared state risk"), +] + +# Patterns indicating cryptographic code +CRYPTO_PATTERNS = [ + (r"\bEVP_|SSL_|HMAC_|SHA[0-9]|AES_|RSA_|EC_KEY", "Cryptographic operations"), + (r"#include\s* FileAssessment: + """Analyze a single C/C++ source file for migration assessment.""" + assessment = FileAssessment(filepath=filepath) + + try: + with open(filepath, "r", encoding="utf-8", errors="replace") as f: + content = f.read() + lines = content.split("\n") + except IOError as e: + assessment.migration_notes.append(f"Could not read file: {e}") + return assessment + + assessment.lines_of_code = len([l for l in lines if l.strip() and not l.strip().startswith("//")]) + + score = 0 + + # Check unsafe functions + for func_name, (description, weight) in UNSAFE_FUNCTIONS.items(): + pattern = rf"\b{re.escape(func_name)}\s*\(" + matches = re.findall(pattern, content) + if matches: + count = len(matches) + assessment.unsafe_function_calls.append({ + "function": func_name, + "count": count, + "risk": description, + "weight": weight, + }) + score += weight * min(count, 5) # Cap per-function contribution + + # Check network exposure + for pattern, description in NETWORK_PATTERNS: + if re.search(pattern, content): + assessment.network_indicators.append(description) + score += 4 + + # Check input processing + for pattern, description in INPUT_PATTERNS: + if re.search(pattern, content): + assessment.input_indicators.append(description) + score += 2 + + # Check concurrency + for pattern, description in CONCURRENCY_PATTERNS: + if re.search(pattern, content): + assessment.concurrency_indicators.append(description) + score += 3 + + # Check crypto + for pattern, description in CRYPTO_PATTERNS: + if re.search(pattern, content, re.IGNORECASE): + assessment.crypto_indicators.append(description) + score += 3 + + # Check pointer arithmetic + for pattern, description in POINTER_PATTERNS: + matches = re.findall(pattern, content) + if matches: + assessment.pointer_arithmetic.append(f"{description} ({len(matches)} instances)") + score += 2 * min(len(matches), 5) + + # Check buffer declarations + for pattern, description in BUFFER_PATTERNS: + matches = re.findall(pattern, content) + if matches: + assessment.buffer_declarations.append(f"{description} ({len(matches)} instances)") + score += 2 * min(len(matches), 5) + + # Set priority + assessment.risk_score = score + if score >= 30: + assessment.priority = "CRITICAL" + elif score >= 20: + assessment.priority = "HIGH" + elif score >= 10: + assessment.priority = "MEDIUM" + else: + assessment.priority = "LOW" + + # Recommend language + if assessment.network_indicators or assessment.crypto_indicators: + assessment.recommended_language = "Rust (performance-critical, security-sensitive)" + elif assessment.concurrency_indicators: + assessment.recommended_language = "Rust or Go (strong concurrency support)" + elif assessment.lines_of_code > 5000: + assessment.recommended_language = "Rust (large codebase, needs fine-grained control)" + else: + assessment.recommended_language = "Rust or Go (general recommendation)" + + # Add notes + if assessment.lines_of_code > 10000: + assessment.migration_notes.append( + "Large file — consider incremental migration, one function at a time" + ) + if len(assessment.unsafe_function_calls) > 10: + assessment.migration_notes.append( + "Heavy unsafe function usage — high migration impact, high security benefit" + ) + if not assessment.network_indicators and not assessment.input_indicators: + assessment.migration_notes.append( + "No network/input exposure detected — lower attack surface, lower priority" + ) + + return assessment + + +def find_source_files(path: str) -> List[str]: + """Find all C/C++ source files in a path.""" + extensions = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"} + if os.path.isfile(path): + if Path(path).suffix.lower() in extensions: + return [path] + return [] + + sources = [] + for root, _, files in os.walk(path): + for f in files: + if Path(f).suffix.lower() in extensions: + sources.append(os.path.join(root, f)) + return sorted(sources) + + +def print_report(assessments: List[FileAssessment]): + """Print a human-readable migration assessment report.""" + print("=" * 72) + print("MEMORY-SAFE MIGRATION ASSESSMENT REPORT") + print("=" * 72) + print() + + # Summary + total_loc = sum(a.lines_of_code for a in assessments) + critical = [a for a in assessments if a.priority == "CRITICAL"] + high = [a for a in assessments if a.priority == "HIGH"] + medium = [a for a in assessments if a.priority == "MEDIUM"] + low = [a for a in assessments if a.priority == "LOW"] + + print(f"Files analyzed: {len(assessments)}") + print(f"Total lines of code: {total_loc:,}") + print(f" CRITICAL priority: {len(critical)}") + print(f" HIGH priority: {len(high)}") + print(f" MEDIUM priority: {len(medium)}") + print(f" LOW priority: {len(low)}") + print() + + # Sort by risk score descending + assessments_sorted = sorted(assessments, key=lambda a: a.risk_score, reverse=True) + + # Recommended migration order + print("-" * 72) + print("RECOMMENDED MIGRATION ORDER") + print("-" * 72) + for i, a in enumerate(assessments_sorted[:20], 1): + print(f" {i:2d}. [{a.priority:8s}] (score: {a.risk_score:3d}) {a.filepath}") + print(f" {a.lines_of_code:,} LOC | {a.recommended_language}") + if len(assessments_sorted) > 20: + print(f" ... and {len(assessments_sorted) - 20} more files") + print() + + # Detailed per-file reports (top 10 only) + print("-" * 72) + print("DETAILED ASSESSMENT (top 10 by risk)") + print("-" * 72) + for a in assessments_sorted[:10]: + print() + print(f" File: {a.filepath}") + print(f" Lines of code: {a.lines_of_code:,}") + print(f" Risk score: {a.risk_score} ({a.priority})") + print(f" Recommended: {a.recommended_language}") + + if a.unsafe_function_calls: + print(f" Unsafe functions:") + for uf in sorted(a.unsafe_function_calls, key=lambda x: x["weight"], reverse=True)[:8]: + print(f" - {uf['function']}() x{uf['count']}: {uf['risk']}") + + if a.network_indicators: + print(f" Network exposure:") + for n in a.network_indicators[:5]: + print(f" - {n}") + + if a.crypto_indicators: + print(f" Cryptographic code:") + for c in a.crypto_indicators[:3]: + print(f" - {c}") + + if a.pointer_arithmetic: + print(f" Pointer arithmetic: {', '.join(a.pointer_arithmetic[:3])}") + + if a.buffer_declarations: + print(f" Buffer declarations: {', '.join(a.buffer_declarations[:3])}") + + if a.migration_notes: + print(f" Notes:") + for n in a.migration_notes: + print(f" - {n}") + + print() + print("=" * 72) + print("Assessment complete. Migrate CRITICAL and HIGH priority files first.") + print("Write tests for each component BEFORE starting migration.") + print("=" * 72) + + +def main(): + parser = argparse.ArgumentParser( + description="Assess C/C++ code for memory-safe language migration" + ) + parser.add_argument( + "--file", "-f", required=True, + help="Path to a C/C++ source file or directory to analyze" + ) + parser.add_argument( + "--format", choices=["text", "json"], default="text", + help="Output format (default: text)" + ) + args = parser.parse_args() + + source_files = find_source_files(args.file) + if not source_files: + print(f"No C/C++ source files found in: {args.file}", file=sys.stderr) + sys.exit(1) + + assessments = [analyze_file(f) for f in source_files] + + if args.format == "json": + output = { + "summary": { + "files_analyzed": len(assessments), + "total_loc": sum(a.lines_of_code for a in assessments), + "critical": len([a for a in assessments if a.priority == "CRITICAL"]), + "high": len([a for a in assessments if a.priority == "HIGH"]), + "medium": len([a for a in assessments if a.priority == "MEDIUM"]), + "low": len([a for a in assessments if a.priority == "LOW"]), + }, + "files": [asdict(a) for a in sorted( + assessments, key=lambda a: a.risk_score, reverse=True + )], + } + print(json.dumps(output, indent=2)) + else: + print_report(assessments) + + +if __name__ == "__main__": + main() diff --git a/sources/owasp/codeguard-0-ajax-security.md b/sources/additional-skills/owasp/codeguard-0-ajax-security.md similarity index 100% rename from sources/owasp/codeguard-0-ajax-security.md rename to sources/additional-skills/owasp/codeguard-0-ajax-security.md diff --git a/sources/owasp/codeguard-0-attack-surface-analysis.md b/sources/additional-skills/owasp/codeguard-0-attack-surface-analysis.md similarity index 100% rename from sources/owasp/codeguard-0-attack-surface-analysis.md rename to sources/additional-skills/owasp/codeguard-0-attack-surface-analysis.md diff --git a/sources/owasp/codeguard-0-authentication.md b/sources/additional-skills/owasp/codeguard-0-authentication.md similarity index 100% rename from sources/owasp/codeguard-0-authentication.md rename to sources/additional-skills/owasp/codeguard-0-authentication.md diff --git a/sources/owasp/codeguard-0-authorization-testing-automation.md b/sources/additional-skills/owasp/codeguard-0-authorization-testing-automation.md similarity index 100% rename from sources/owasp/codeguard-0-authorization-testing-automation.md rename to sources/additional-skills/owasp/codeguard-0-authorization-testing-automation.md diff --git a/sources/owasp/codeguard-0-authorization.md b/sources/additional-skills/owasp/codeguard-0-authorization.md similarity index 100% rename from sources/owasp/codeguard-0-authorization.md rename to sources/additional-skills/owasp/codeguard-0-authorization.md diff --git a/sources/owasp/codeguard-0-bean-validation.md b/sources/additional-skills/owasp/codeguard-0-bean-validation.md similarity index 100% rename from sources/owasp/codeguard-0-bean-validation.md rename to sources/additional-skills/owasp/codeguard-0-bean-validation.md diff --git a/sources/owasp/codeguard-0-browser-extension-vulnerabilities.md b/sources/additional-skills/owasp/codeguard-0-browser-extension-vulnerabilities.md similarity index 100% rename from sources/owasp/codeguard-0-browser-extension-vulnerabilities.md rename to sources/additional-skills/owasp/codeguard-0-browser-extension-vulnerabilities.md diff --git a/sources/owasp/codeguard-0-c-based-toolchain-hardening.md b/sources/additional-skills/owasp/codeguard-0-c-based-toolchain-hardening.md similarity index 100% rename from sources/owasp/codeguard-0-c-based-toolchain-hardening.md rename to sources/additional-skills/owasp/codeguard-0-c-based-toolchain-hardening.md diff --git a/sources/owasp/codeguard-0-choosing-and-using-security-questions.md b/sources/additional-skills/owasp/codeguard-0-choosing-and-using-security-questions.md similarity index 100% rename from sources/owasp/codeguard-0-choosing-and-using-security-questions.md rename to sources/additional-skills/owasp/codeguard-0-choosing-and-using-security-questions.md diff --git a/sources/owasp/codeguard-0-ci-cd-security.md b/sources/additional-skills/owasp/codeguard-0-ci-cd-security.md similarity index 100% rename from sources/owasp/codeguard-0-ci-cd-security.md rename to sources/additional-skills/owasp/codeguard-0-ci-cd-security.md diff --git a/sources/owasp/codeguard-0-clickjacking-defense.md b/sources/additional-skills/owasp/codeguard-0-clickjacking-defense.md similarity index 100% rename from sources/owasp/codeguard-0-clickjacking-defense.md rename to sources/additional-skills/owasp/codeguard-0-clickjacking-defense.md diff --git a/sources/owasp/codeguard-0-content-security-policy.md b/sources/additional-skills/owasp/codeguard-0-content-security-policy.md similarity index 100% rename from sources/owasp/codeguard-0-content-security-policy.md rename to sources/additional-skills/owasp/codeguard-0-content-security-policy.md diff --git a/sources/owasp/codeguard-0-cookie-theft-mitigation.md b/sources/additional-skills/owasp/codeguard-0-cookie-theft-mitigation.md similarity index 100% rename from sources/owasp/codeguard-0-cookie-theft-mitigation.md rename to sources/additional-skills/owasp/codeguard-0-cookie-theft-mitigation.md diff --git a/sources/owasp/codeguard-0-credential-stuffing-prevention.md b/sources/additional-skills/owasp/codeguard-0-credential-stuffing-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-credential-stuffing-prevention.md rename to sources/additional-skills/owasp/codeguard-0-credential-stuffing-prevention.md diff --git a/sources/owasp/codeguard-0-cross-site-request-forgery-prevention.md b/sources/additional-skills/owasp/codeguard-0-cross-site-request-forgery-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-cross-site-request-forgery-prevention.md rename to sources/additional-skills/owasp/codeguard-0-cross-site-request-forgery-prevention.md diff --git a/sources/owasp/codeguard-0-cross-site-scripting-prevention.md b/sources/additional-skills/owasp/codeguard-0-cross-site-scripting-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-cross-site-scripting-prevention.md rename to sources/additional-skills/owasp/codeguard-0-cross-site-scripting-prevention.md diff --git a/sources/owasp/codeguard-0-cryptographic-storage.md b/sources/additional-skills/owasp/codeguard-0-cryptographic-storage.md similarity index 100% rename from sources/owasp/codeguard-0-cryptographic-storage.md rename to sources/additional-skills/owasp/codeguard-0-cryptographic-storage.md diff --git a/sources/owasp/codeguard-0-cw-cryptographic-security-guidelines.md b/sources/additional-skills/owasp/codeguard-0-cw-cryptographic-security-guidelines.md similarity index 100% rename from sources/owasp/codeguard-0-cw-cryptographic-security-guidelines.md rename to sources/additional-skills/owasp/codeguard-0-cw-cryptographic-security-guidelines.md diff --git a/sources/owasp/codeguard-0-cw-memory-string-usage-guidelines.md b/sources/additional-skills/owasp/codeguard-0-cw-memory-string-usage-guidelines.md similarity index 100% rename from sources/owasp/codeguard-0-cw-memory-string-usage-guidelines.md rename to sources/additional-skills/owasp/codeguard-0-cw-memory-string-usage-guidelines.md diff --git a/sources/owasp/codeguard-0-database-security.md b/sources/additional-skills/owasp/codeguard-0-database-security.md similarity index 100% rename from sources/owasp/codeguard-0-database-security.md rename to sources/additional-skills/owasp/codeguard-0-database-security.md diff --git a/sources/owasp/codeguard-0-deserialization.md b/sources/additional-skills/owasp/codeguard-0-deserialization.md similarity index 100% rename from sources/owasp/codeguard-0-deserialization.md rename to sources/additional-skills/owasp/codeguard-0-deserialization.md diff --git a/sources/owasp/codeguard-0-django-rest-framework.md b/sources/additional-skills/owasp/codeguard-0-django-rest-framework.md similarity index 100% rename from sources/owasp/codeguard-0-django-rest-framework.md rename to sources/additional-skills/owasp/codeguard-0-django-rest-framework.md diff --git a/sources/owasp/codeguard-0-django-security.md b/sources/additional-skills/owasp/codeguard-0-django-security.md similarity index 100% rename from sources/owasp/codeguard-0-django-security.md rename to sources/additional-skills/owasp/codeguard-0-django-security.md diff --git a/sources/owasp/codeguard-0-docker-security.md b/sources/additional-skills/owasp/codeguard-0-docker-security.md similarity index 100% rename from sources/owasp/codeguard-0-docker-security.md rename to sources/additional-skills/owasp/codeguard-0-docker-security.md diff --git a/sources/owasp/codeguard-0-dom-based-xss-prevention.md b/sources/additional-skills/owasp/codeguard-0-dom-based-xss-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-dom-based-xss-prevention.md rename to sources/additional-skills/owasp/codeguard-0-dom-based-xss-prevention.md diff --git a/sources/owasp/codeguard-0-dom-clobbering-prevention.md b/sources/additional-skills/owasp/codeguard-0-dom-clobbering-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-dom-clobbering-prevention.md rename to sources/additional-skills/owasp/codeguard-0-dom-clobbering-prevention.md diff --git a/sources/owasp/codeguard-0-dotnet-security.md b/sources/additional-skills/owasp/codeguard-0-dotnet-security.md similarity index 100% rename from sources/owasp/codeguard-0-dotnet-security.md rename to sources/additional-skills/owasp/codeguard-0-dotnet-security.md diff --git a/sources/owasp/codeguard-0-error-handling.md b/sources/additional-skills/owasp/codeguard-0-error-handling.md similarity index 100% rename from sources/owasp/codeguard-0-error-handling.md rename to sources/additional-skills/owasp/codeguard-0-error-handling.md diff --git a/sources/owasp/codeguard-0-file-upload.md b/sources/additional-skills/owasp/codeguard-0-file-upload.md similarity index 100% rename from sources/owasp/codeguard-0-file-upload.md rename to sources/additional-skills/owasp/codeguard-0-file-upload.md diff --git a/sources/owasp/codeguard-0-forgot-password.md b/sources/additional-skills/owasp/codeguard-0-forgot-password.md similarity index 100% rename from sources/owasp/codeguard-0-forgot-password.md rename to sources/additional-skills/owasp/codeguard-0-forgot-password.md diff --git a/sources/owasp/codeguard-0-graphql.md b/sources/additional-skills/owasp/codeguard-0-graphql.md similarity index 100% rename from sources/owasp/codeguard-0-graphql.md rename to sources/additional-skills/owasp/codeguard-0-graphql.md diff --git a/sources/owasp/codeguard-0-html5-security.md b/sources/additional-skills/owasp/codeguard-0-html5-security.md similarity index 100% rename from sources/owasp/codeguard-0-html5-security.md rename to sources/additional-skills/owasp/codeguard-0-html5-security.md diff --git a/sources/owasp/codeguard-0-http-headers.md b/sources/additional-skills/owasp/codeguard-0-http-headers.md similarity index 100% rename from sources/owasp/codeguard-0-http-headers.md rename to sources/additional-skills/owasp/codeguard-0-http-headers.md diff --git a/sources/owasp/codeguard-0-http-strict-transport-security.md b/sources/additional-skills/owasp/codeguard-0-http-strict-transport-security.md similarity index 100% rename from sources/owasp/codeguard-0-http-strict-transport-security.md rename to sources/additional-skills/owasp/codeguard-0-http-strict-transport-security.md diff --git a/sources/owasp/codeguard-0-injection-prevention.md b/sources/additional-skills/owasp/codeguard-0-injection-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-injection-prevention.md rename to sources/additional-skills/owasp/codeguard-0-injection-prevention.md diff --git a/sources/owasp/codeguard-0-input-validation.md b/sources/additional-skills/owasp/codeguard-0-input-validation.md similarity index 100% rename from sources/owasp/codeguard-0-input-validation.md rename to sources/additional-skills/owasp/codeguard-0-input-validation.md diff --git a/sources/owasp/codeguard-0-insecure-direct-object-reference-prevention.md b/sources/additional-skills/owasp/codeguard-0-insecure-direct-object-reference-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-insecure-direct-object-reference-prevention.md rename to sources/additional-skills/owasp/codeguard-0-insecure-direct-object-reference-prevention.md diff --git a/sources/owasp/codeguard-0-jaas.md b/sources/additional-skills/owasp/codeguard-0-jaas.md similarity index 100% rename from sources/owasp/codeguard-0-jaas.md rename to sources/additional-skills/owasp/codeguard-0-jaas.md diff --git a/sources/owasp/codeguard-0-java-security.md b/sources/additional-skills/owasp/codeguard-0-java-security.md similarity index 100% rename from sources/owasp/codeguard-0-java-security.md rename to sources/additional-skills/owasp/codeguard-0-java-security.md diff --git a/sources/owasp/codeguard-0-json-web-token-for-java.md b/sources/additional-skills/owasp/codeguard-0-json-web-token-for-java.md similarity index 100% rename from sources/owasp/codeguard-0-json-web-token-for-java.md rename to sources/additional-skills/owasp/codeguard-0-json-web-token-for-java.md diff --git a/sources/owasp/codeguard-0-key-management.md b/sources/additional-skills/owasp/codeguard-0-key-management.md similarity index 100% rename from sources/owasp/codeguard-0-key-management.md rename to sources/additional-skills/owasp/codeguard-0-key-management.md diff --git a/sources/owasp/codeguard-0-kubernetes-security.md b/sources/additional-skills/owasp/codeguard-0-kubernetes-security.md similarity index 100% rename from sources/owasp/codeguard-0-kubernetes-security.md rename to sources/additional-skills/owasp/codeguard-0-kubernetes-security.md diff --git a/sources/owasp/codeguard-0-laravel.md b/sources/additional-skills/owasp/codeguard-0-laravel.md similarity index 100% rename from sources/owasp/codeguard-0-laravel.md rename to sources/additional-skills/owasp/codeguard-0-laravel.md diff --git a/sources/owasp/codeguard-0-ldap-injection-prevention.md b/sources/additional-skills/owasp/codeguard-0-ldap-injection-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-ldap-injection-prevention.md rename to sources/additional-skills/owasp/codeguard-0-ldap-injection-prevention.md diff --git a/sources/owasp/codeguard-0-legacy-application-management.md b/sources/additional-skills/owasp/codeguard-0-legacy-application-management.md similarity index 100% rename from sources/owasp/codeguard-0-legacy-application-management.md rename to sources/additional-skills/owasp/codeguard-0-legacy-application-management.md diff --git a/sources/owasp/codeguard-0-logging-vocabulary.md b/sources/additional-skills/owasp/codeguard-0-logging-vocabulary.md similarity index 100% rename from sources/owasp/codeguard-0-logging-vocabulary.md rename to sources/additional-skills/owasp/codeguard-0-logging-vocabulary.md diff --git a/sources/owasp/codeguard-0-mass-assignment.md b/sources/additional-skills/owasp/codeguard-0-mass-assignment.md similarity index 100% rename from sources/owasp/codeguard-0-mass-assignment.md rename to sources/additional-skills/owasp/codeguard-0-mass-assignment.md diff --git a/sources/owasp/codeguard-0-microservices-security.md b/sources/additional-skills/owasp/codeguard-0-microservices-security.md similarity index 100% rename from sources/owasp/codeguard-0-microservices-security.md rename to sources/additional-skills/owasp/codeguard-0-microservices-security.md diff --git a/sources/owasp/codeguard-0-mobile-application-security.md b/sources/additional-skills/owasp/codeguard-0-mobile-application-security.md similarity index 100% rename from sources/owasp/codeguard-0-mobile-application-security.md rename to sources/additional-skills/owasp/codeguard-0-mobile-application-security.md diff --git a/sources/owasp/codeguard-0-multifactor-authentication.md b/sources/additional-skills/owasp/codeguard-0-multifactor-authentication.md similarity index 100% rename from sources/owasp/codeguard-0-multifactor-authentication.md rename to sources/additional-skills/owasp/codeguard-0-multifactor-authentication.md diff --git a/sources/owasp/codeguard-0-network-segmentation.md b/sources/additional-skills/owasp/codeguard-0-network-segmentation.md similarity index 100% rename from sources/owasp/codeguard-0-network-segmentation.md rename to sources/additional-skills/owasp/codeguard-0-network-segmentation.md diff --git a/sources/owasp/codeguard-0-nodejs-docker.md b/sources/additional-skills/owasp/codeguard-0-nodejs-docker.md similarity index 100% rename from sources/owasp/codeguard-0-nodejs-docker.md rename to sources/additional-skills/owasp/codeguard-0-nodejs-docker.md diff --git a/sources/owasp/codeguard-0-nodejs-security.md b/sources/additional-skills/owasp/codeguard-0-nodejs-security.md similarity index 100% rename from sources/owasp/codeguard-0-nodejs-security.md rename to sources/additional-skills/owasp/codeguard-0-nodejs-security.md diff --git a/sources/owasp/codeguard-0-npm-security.md b/sources/additional-skills/owasp/codeguard-0-npm-security.md similarity index 100% rename from sources/owasp/codeguard-0-npm-security.md rename to sources/additional-skills/owasp/codeguard-0-npm-security.md diff --git a/sources/owasp/codeguard-0-oauth2.md b/sources/additional-skills/owasp/codeguard-0-oauth2.md similarity index 100% rename from sources/owasp/codeguard-0-oauth2.md rename to sources/additional-skills/owasp/codeguard-0-oauth2.md diff --git a/sources/owasp/codeguard-0-open-redirect.md b/sources/additional-skills/owasp/codeguard-0-open-redirect.md similarity index 100% rename from sources/owasp/codeguard-0-open-redirect.md rename to sources/additional-skills/owasp/codeguard-0-open-redirect.md diff --git a/sources/owasp/codeguard-0-os-command-injection-defense.md b/sources/additional-skills/owasp/codeguard-0-os-command-injection-defense.md similarity index 100% rename from sources/owasp/codeguard-0-os-command-injection-defense.md rename to sources/additional-skills/owasp/codeguard-0-os-command-injection-defense.md diff --git a/sources/owasp/codeguard-0-password-storage.md b/sources/additional-skills/owasp/codeguard-0-password-storage.md similarity index 100% rename from sources/owasp/codeguard-0-password-storage.md rename to sources/additional-skills/owasp/codeguard-0-password-storage.md diff --git a/sources/owasp/codeguard-0-php-configuration.md b/sources/additional-skills/owasp/codeguard-0-php-configuration.md similarity index 100% rename from sources/owasp/codeguard-0-php-configuration.md rename to sources/additional-skills/owasp/codeguard-0-php-configuration.md diff --git a/sources/owasp/codeguard-0-pinning.md b/sources/additional-skills/owasp/codeguard-0-pinning.md similarity index 100% rename from sources/owasp/codeguard-0-pinning.md rename to sources/additional-skills/owasp/codeguard-0-pinning.md diff --git a/sources/owasp/codeguard-0-prototype-pollution-prevention.md b/sources/additional-skills/owasp/codeguard-0-prototype-pollution-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-prototype-pollution-prevention.md rename to sources/additional-skills/owasp/codeguard-0-prototype-pollution-prevention.md diff --git a/sources/owasp/codeguard-0-query-parameterization.md b/sources/additional-skills/owasp/codeguard-0-query-parameterization.md similarity index 100% rename from sources/owasp/codeguard-0-query-parameterization.md rename to sources/additional-skills/owasp/codeguard-0-query-parameterization.md diff --git a/sources/owasp/codeguard-0-rest-assessment.md b/sources/additional-skills/owasp/codeguard-0-rest-assessment.md similarity index 100% rename from sources/owasp/codeguard-0-rest-assessment.md rename to sources/additional-skills/owasp/codeguard-0-rest-assessment.md diff --git a/sources/owasp/codeguard-0-rest-security.md b/sources/additional-skills/owasp/codeguard-0-rest-security.md similarity index 100% rename from sources/owasp/codeguard-0-rest-security.md rename to sources/additional-skills/owasp/codeguard-0-rest-security.md diff --git a/sources/owasp/codeguard-0-ruby-on-rails.md b/sources/additional-skills/owasp/codeguard-0-ruby-on-rails.md similarity index 100% rename from sources/owasp/codeguard-0-ruby-on-rails.md rename to sources/additional-skills/owasp/codeguard-0-ruby-on-rails.md diff --git a/sources/owasp/codeguard-0-safe-c-functions.md b/sources/additional-skills/owasp/codeguard-0-safe-c-functions.md similarity index 100% rename from sources/owasp/codeguard-0-safe-c-functions.md rename to sources/additional-skills/owasp/codeguard-0-safe-c-functions.md diff --git a/sources/owasp/codeguard-0-saml-security.md b/sources/additional-skills/owasp/codeguard-0-saml-security.md similarity index 100% rename from sources/owasp/codeguard-0-saml-security.md rename to sources/additional-skills/owasp/codeguard-0-saml-security.md diff --git a/sources/owasp/codeguard-0-securing-cascading-style-sheets.md b/sources/additional-skills/owasp/codeguard-0-securing-cascading-style-sheets.md similarity index 100% rename from sources/owasp/codeguard-0-securing-cascading-style-sheets.md rename to sources/additional-skills/owasp/codeguard-0-securing-cascading-style-sheets.md diff --git a/sources/owasp/codeguard-0-server-side-request-forgery-prevention.md b/sources/additional-skills/owasp/codeguard-0-server-side-request-forgery-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-server-side-request-forgery-prevention.md rename to sources/additional-skills/owasp/codeguard-0-server-side-request-forgery-prevention.md diff --git a/sources/owasp/codeguard-0-session-management.md b/sources/additional-skills/owasp/codeguard-0-session-management.md similarity index 100% rename from sources/owasp/codeguard-0-session-management.md rename to sources/additional-skills/owasp/codeguard-0-session-management.md diff --git a/sources/owasp/codeguard-0-sql-injection-prevention.md b/sources/additional-skills/owasp/codeguard-0-sql-injection-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-sql-injection-prevention.md rename to sources/additional-skills/owasp/codeguard-0-sql-injection-prevention.md diff --git a/sources/owasp/codeguard-0-symfony.md b/sources/additional-skills/owasp/codeguard-0-symfony.md similarity index 100% rename from sources/owasp/codeguard-0-symfony.md rename to sources/additional-skills/owasp/codeguard-0-symfony.md diff --git a/sources/owasp/codeguard-0-third-party-javascript-management.md b/sources/additional-skills/owasp/codeguard-0-third-party-javascript-management.md similarity index 100% rename from sources/owasp/codeguard-0-third-party-javascript-management.md rename to sources/additional-skills/owasp/codeguard-0-third-party-javascript-management.md diff --git a/sources/owasp/codeguard-0-threat-modeling.md b/sources/additional-skills/owasp/codeguard-0-threat-modeling.md similarity index 100% rename from sources/owasp/codeguard-0-threat-modeling.md rename to sources/additional-skills/owasp/codeguard-0-threat-modeling.md diff --git a/sources/owasp/codeguard-0-transaction-authorization.md b/sources/additional-skills/owasp/codeguard-0-transaction-authorization.md similarity index 100% rename from sources/owasp/codeguard-0-transaction-authorization.md rename to sources/additional-skills/owasp/codeguard-0-transaction-authorization.md diff --git a/sources/owasp/codeguard-0-transport-layer-security.md b/sources/additional-skills/owasp/codeguard-0-transport-layer-security.md similarity index 100% rename from sources/owasp/codeguard-0-transport-layer-security.md rename to sources/additional-skills/owasp/codeguard-0-transport-layer-security.md diff --git a/sources/owasp/codeguard-0-unvalidated-redirects-and-forwards.md b/sources/additional-skills/owasp/codeguard-0-unvalidated-redirects-and-forwards.md similarity index 100% rename from sources/owasp/codeguard-0-unvalidated-redirects-and-forwards.md rename to sources/additional-skills/owasp/codeguard-0-unvalidated-redirects-and-forwards.md diff --git a/sources/owasp/codeguard-0-user-privacy-protection.md b/sources/additional-skills/owasp/codeguard-0-user-privacy-protection.md similarity index 100% rename from sources/owasp/codeguard-0-user-privacy-protection.md rename to sources/additional-skills/owasp/codeguard-0-user-privacy-protection.md diff --git a/sources/owasp/codeguard-0-virtual-patching.md b/sources/additional-skills/owasp/codeguard-0-virtual-patching.md similarity index 100% rename from sources/owasp/codeguard-0-virtual-patching.md rename to sources/additional-skills/owasp/codeguard-0-virtual-patching.md diff --git a/sources/owasp/codeguard-0-vulnerable-dependency-management.md b/sources/additional-skills/owasp/codeguard-0-vulnerable-dependency-management.md similarity index 100% rename from sources/owasp/codeguard-0-vulnerable-dependency-management.md rename to sources/additional-skills/owasp/codeguard-0-vulnerable-dependency-management.md diff --git a/sources/owasp/codeguard-0-web-service-security.md b/sources/additional-skills/owasp/codeguard-0-web-service-security.md similarity index 100% rename from sources/owasp/codeguard-0-web-service-security.md rename to sources/additional-skills/owasp/codeguard-0-web-service-security.md diff --git a/sources/owasp/codeguard-0-xml-external-entity-prevention.md b/sources/additional-skills/owasp/codeguard-0-xml-external-entity-prevention.md similarity index 100% rename from sources/owasp/codeguard-0-xml-external-entity-prevention.md rename to sources/additional-skills/owasp/codeguard-0-xml-external-entity-prevention.md diff --git a/sources/owasp/codeguard-0-xml-security.md b/sources/additional-skills/owasp/codeguard-0-xml-security.md similarity index 100% rename from sources/owasp/codeguard-0-xml-security.md rename to sources/additional-skills/owasp/codeguard-0-xml-security.md diff --git a/sources/owasp/codeguard-0-xs-leaks.md b/sources/additional-skills/owasp/codeguard-0-xs-leaks.md similarity index 100% rename from sources/owasp/codeguard-0-xs-leaks.md rename to sources/additional-skills/owasp/codeguard-0-xs-leaks.md diff --git a/sources/owasp/codeguard-0-xss-filter-evasion.md b/sources/additional-skills/owasp/codeguard-0-xss-filter-evasion.md similarity index 100% rename from sources/owasp/codeguard-0-xss-filter-evasion.md rename to sources/additional-skills/owasp/codeguard-0-xss-filter-evasion.md diff --git a/sources/owasp/codeguard-0-zero-trust-architecture.md b/sources/additional-skills/owasp/codeguard-0-zero-trust-architecture.md similarity index 100% rename from sources/owasp/codeguard-0-zero-trust-architecture.md rename to sources/additional-skills/owasp/codeguard-0-zero-trust-architecture.md From 520ae401cb909af31a0d53c9367678e144295c89 Mon Sep 17 00:00:00 2001 From: Ramraj Bishnoie Date: Wed, 15 Apr 2026 14:14:54 -0400 Subject: [PATCH 2/4] updating references to additional owasp skills based on dir refactor --- docs/claude-code-skill-plugin.md | 5 +- docs/custom-rules.md | 9 +- docs/faq.md | 4 +- docs/getting-started.md | 4 +- .../scripts/assess-migration.py | 2 +- uv.lock | 196 ++++++++++-------- 6 files changed, 127 insertions(+), 93 deletions(-) diff --git a/docs/claude-code-skill-plugin.md b/docs/claude-code-skill-plugin.md index 8f242a6..a395974 100644 --- a/docs/claude-code-skill-plugin.md +++ b/docs/claude-code-skill-plugin.md @@ -292,7 +292,7 @@ This command: - Generates `skills/` directory with the 23 core security rules (Claude Code plugin) - Creates `dist/` with all supported agent-specific formats -**Note:** The Claude Code plugin (`skills/`) always contains only the 23 curated core rules. To build bundles with OWASP supplementary rules for other IDEs, use `--source core owasp`, but this only affects `dist/`, not `skills/`. +**Note:** The Claude Code plugin (`skills/`) always contains only the 23 curated core rules. To build bundles with OWASP supplementary rules for other IDEs, use `--source core additional-skills/owasp`, but this only affects `dist/`, not `skills/`. ## Advanced Usage @@ -338,7 +338,8 @@ cosai-oasis/project-codeguard/ │ ├── sources/ # Source rules (version controlled) │ ├── core/ # Core security rules -│ └── owasp/ # OWASP supplementary rules +│ └── additional-skills/ +│ └── owasp/ # OWASP supplementary rules │ ├── skills/ # Claude Code plugin (version controlled) │ └── software-security/ diff --git a/docs/custom-rules.md b/docs/custom-rules.md index bb9fe5c..f612003 100644 --- a/docs/custom-rules.md +++ b/docs/custom-rules.md @@ -7,9 +7,10 @@ Create custom rules to enforce your own policies, compliance requirements, or co 1. **Create a source folder** under `sources/`: sources/ - core/ # Project CodeGuard rules - owasp/ # OWASP supplementary rules - my-rules/ # Your custom rules + core/ # Project CodeGuard rules + additional-skills/ + owasp/ # OWASP supplementary rules + my-rules/ # Your custom rules 2. **Copy the template** from `sources/templates/custom-rule-template.md.example` and customize it @@ -45,7 +46,7 @@ Converts source rules to IDE-specific formats. uv run python src/convert_to_ide_formats.py # Include multiple sources -uv run python src/convert_to_ide_formats.py --source core owasp my-rules +uv run python src/convert_to_ide_formats.py --source core additional-skills/owasp my-rules # Custom output directory uv run python src/convert_to_ide_formats.py --source core my-rules -o build diff --git a/docs/faq.md b/docs/faq.md index a0d5096..a9019f1 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -50,12 +50,12 @@ This FAQ document provides clear, concise answers to help developers seamlessly ## Q: What are the OWASP supplementary rules? -**A:** The `sources/owasp/` folder contains supplementary rules based on OWASP guidance that informed the original rule development. These rules are optional, not enabled by default, and are intended primarily for reference and deeper security review use cases. +**A:** The `sources/additional-skills/owasp/` folder contains supplementary rules based on OWASP guidance that informed the original rule development. These rules are optional, not enabled by default, and are intended primarily for reference and deeper security review use cases. The official release bundles package the main `sources/core/` rules. If you [build from source](getting-started.md#option-2-build-from-source) and want the OWASP supplementary set too, include it explicitly: ```bash -uv run python src/convert_to_ide_formats.py --source core owasp +uv run python src/convert_to_ide_formats.py --source core additional-skills/owasp ``` --- diff --git a/docs/getting-started.md b/docs/getting-started.md index f38c0ac..736eb96 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -269,7 +269,7 @@ uv run python src/validate_unified_rules.py sources/ uv run python src/convert_to_ide_formats.py # Or include all rules (core + owasp supplementary) -uv run python src/convert_to_ide_formats.py --source core owasp +uv run python src/convert_to_ide_formats.py --source core additional-skills/owasp # Copy the generated rules to your project cp -r dist/.cursor/ /path/to/your/project/ @@ -287,7 +287,7 @@ cp -r dist/.hermes/ /path/to/your/project/ Project CodeGuard has two source rule sets: - `sources/core/`: Official Project CodeGuard rules. These are the main rules packaged in releases and enabled by default. -- `sources/owasp/`: Supplementary rules originally derived from OWASP guidance. These are optional and are not enabled by default. +- `sources/additional-skills/owasp/`: Supplementary rules originally derived from OWASP guidance. These are optional and are not enabled by default. Use OWASP supplementary rules when you explicitly want broader coverage, such as deeper security reviews or reference-driven review workflows. diff --git a/sources/additional-skills/memory-safe-migration/scripts/assess-migration.py b/sources/additional-skills/memory-safe-migration/scripts/assess-migration.py index b9dd988..b12c036 100644 --- a/sources/additional-skills/memory-safe-migration/scripts/assess-migration.py +++ b/sources/additional-skills/memory-safe-migration/scripts/assess-migration.py @@ -86,7 +86,7 @@ (r"\bopen\s*\(", "File descriptor open"), (r"\bread\s*\(", "Low-level read — may process untrusted data"), (r"\bfscanf\s*\(", "Formatted file input"), - (r"\bxml|json|yaml|parse", "Data parsing — untrusted input risk"), + (r"\b(xml|json|yaml|parse)\b", "Data parsing — untrusted input risk"), ] # Patterns indicating concurrency diff --git a/uv.lock b/uv.lock index 378cb3b..44300cf 100644 --- a/uv.lock +++ b/uv.lock @@ -1,11 +1,7 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.11" -[options] -exclude-newer = "2026-03-31T03:55:16.725696Z" -exclude-newer-span = "P7D" - [manifest] constraints = [ { name = "pygments", specifier = ">=2.20.0" }, @@ -15,11 +11,11 @@ constraints = [ [[package]] name = "babel" -version = "2.17.0" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] @@ -38,76 +34,112 @@ wheels = [ [[package]] name = "certifi" -version = "2025.10.5" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.3" +version = "3.4.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, - { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, - { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, - { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, - { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, - { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, - { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, - { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, - { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, - { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, - { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, - { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, - { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, - { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, - { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, - { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, - { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, - { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, - { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, - { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, - { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, - { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, - { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, - { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, - { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, - { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, - { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] name = "click" -version = "8.3.0" +version = "8.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, ] [[package]] @@ -154,11 +186,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.9" +version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] @@ -270,21 +302,21 @@ wheels = [ [[package]] name = "mkdocs-get-deps" -version = "0.2.0" +version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mergedeep" }, { name = "platformdirs" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, ] [[package]] name = "mkdocs-material" -version = "9.6.21" +version = "9.6.23" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -299,9 +331,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/d5/ab83ca9aa314954b0a9e8849780bdd01866a3cfcb15ffb7e3a61ca06ff0b/mkdocs_material-9.6.21.tar.gz", hash = "sha256:b01aa6d2731322438056f360f0e623d3faae981f8f2d8c68b1b973f4f2657870", size = 4043097, upload-time = "2025-09-30T19:11:27.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/de/cc1d5139c2782b1a49e1ed1845b3298ed6076b9ba1c740ad7c952d8ffcf9/mkdocs_material-9.6.23.tar.gz", hash = "sha256:62ebc9cdbe90e1ae4f4e9b16a6aa5c69b93474c7b9e79ebc0b11b87f9f055e00", size = 4048130, upload-time = "2025-11-01T16:33:11.782Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/4f/98681c2030375fe9b057dbfb9008b68f46c07dddf583f4df09bf8075e37f/mkdocs_material-9.6.21-py3-none-any.whl", hash = "sha256:aa6a5ab6fb4f6d381588ac51da8782a4d3757cb3d1b174f81a2ec126e1f22c92", size = 9203097, upload-time = "2025-09-30T19:11:24.063Z" }, + { url = "https://files.pythonhosted.org/packages/f5/df/bc583e857174b0dc6df67d555123533f09e7e1ac0f3fae7693fb6840c0a3/mkdocs_material-9.6.23-py3-none-any.whl", hash = "sha256:3bf3f1d82d269f3a14ed6897bfc3a844cc05e1dc38045386691b91d7e6945332", size = 9210689, upload-time = "2025-11-01T16:33:08.196Z" }, ] [[package]] @@ -315,11 +347,11 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, ] [[package]] @@ -333,20 +365,20 @@ wheels = [ [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.9.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, ] [[package]] @@ -377,15 +409,15 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "10.16.1" +version = "10.21.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/b3/6d2b3f149bc5413b0a29761c2c5832d8ce904a1d7f621e86616d96f505cc/pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91", size = 853277, upload-time = "2025-07-28T16:19:34.167Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, ] [[package]] From 24aef202f1dacb5ec8f79712d5636627c5731e98 Mon Sep 17 00:00:00 2001 From: Ramraj Bishnoie Date: Wed, 15 Apr 2026 14:16:52 -0400 Subject: [PATCH 3/4] Add backward-compatible 'owasp' source alias for CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Existing users and CI jobs using `--source core owasp` will continue to work after the sources/owasp → sources/additional-skills/owasp move. The alias prints an informational note and resolves transparently. Made-with: Cursor --- src/convert_to_ide_formats.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/convert_to_ide_formats.py b/src/convert_to_ide_formats.py index 98a76ee..2ed2af0 100644 --- a/src/convert_to_ide_formats.py +++ b/src/convert_to_ide_formats.py @@ -291,6 +291,11 @@ def convert_rules( return results +SOURCE_ALIASES = { + "owasp": "additional-skills/owasp", +} + + def _resolve_source_paths(args) -> list[Path]: """ Resolve source paths from CLI arguments. @@ -298,7 +303,13 @@ def _resolve_source_paths(args) -> list[Path]: """ # If --source flags provided, resolve under sources/ if args.source: - return [Path("sources") / src for src in args.source] + resolved = [] + for src in args.source: + canonical = SOURCE_ALIASES.get(src, src) + if canonical != src: + print(f"ℹ️ '{src}' is an alias for '{canonical}'") + resolved.append(Path("sources") / canonical) + return resolved # Default: core rules only return [Path("sources/core")] @@ -314,7 +325,7 @@ def _resolve_source_paths(args) -> list[Path]: parser.add_argument( "--source", nargs="+", - help="Named sources under ./sources to convert (e.g., --source core owasp). Default: core", + help="Named sources under ./sources to convert (e.g., --source core additional-skills/owasp). 'owasp' is accepted as a shorthand alias. Default: core", ) parser.add_argument( "--output-dir", From 43de01fb458e92ca78a7eecc45db692e76706f36 Mon Sep 17 00:00:00 2001 From: Ramraj Bishnoie Date: Wed, 15 Apr 2026 14:30:35 -0400 Subject: [PATCH 4/4] Refine validation logic in unified rules script to focus on codeguardrule files --- .github/workflows/validate-rules.yml | 47 ++++++++++++++-------------- src/validate_unified_rules.py | 7 +++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/validate-rules.yml b/.github/workflows/validate-rules.yml index 14b0fd0..615aa54 100644 --- a/.github/workflows/validate-rules.yml +++ b/.github/workflows/validate-rules.yml @@ -68,6 +68,29 @@ jobs: exit 1 fi + - name: Check skills/software-security/ directory is up-to-date + run: | + echo "Checking if committed skills/software-security/ is up-to-date..." + + # Save the generated skill directory only + cp -r skills/software-security skills-committed + + # Regenerate (core rules only, matching default) + uv run python src/convert_to_ide_formats.py + + # Compare only the software-security subtree + if ! diff -r skills/software-security/ skills-committed/ > /dev/null 2>&1; then + echo "❌ skills/software-security/ is out of date!" + echo "Please regenerate by running: python src/convert_to_ide_formats.py" + echo "Then: git add skills/software-security/" + diff -r skills/software-security/ skills-committed/ || true + rm -rf skills-committed + exit 1 + fi + + rm -rf skills-committed + echo "✅ skills/software-security/ is up-to-date" + - name: Test conversion to IDE formats run: | echo "Testing IDE format conversion..." @@ -116,30 +139,6 @@ jobs: echo "✅ All IDE formats generated successfully" - - name: Check skills/ directory is up-to-date - run: | - echo "Checking if committed skills/ directory is up-to-date..." - - # Save current skills - mv skills skills-committed - - # Regenerate skills (core rules only, matching default) - uv run python src/convert_to_ide_formats.py - - # Compare - if ! diff -r skills/ skills-committed/ > /dev/null 2>&1; then - echo "❌ The skills/ directory is out of date!" - echo "Please regenerate by running: python src/convert_to_ide_formats.py" - echo "Then: git add skills/" - mv skills-committed skills - exit 1 - fi - - # Restore original - rm -rf skills - mv skills-committed skills - echo "✅ Committed skills/ directory is up-to-date" - - name: Summary if: success() run: | diff --git a/src/validate_unified_rules.py b/src/validate_unified_rules.py index 7ef5b10..0ccf96b 100755 --- a/src/validate_unified_rules.py +++ b/src/validate_unified_rules.py @@ -80,10 +80,11 @@ def main(): print(f"❌ Directory {rules_dir} does not exist") sys.exit(1) - # Find all .md files recursively (excluding README and templates) + # Only validate codeguard rule files (codeguard-*.md), skipping READMEs, + # templates, skill manifests, and reference docs md_files = [ - f for f in rules_dir.rglob("*.md") - if f.name.lower() != "readme.md" and not f.name.endswith(".template") + f for f in rules_dir.rglob("codeguard-*.md") + if not f.name.endswith(".template") ] if not md_files: