diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 87aae0c..98c768f 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3271,4 +3271,13 @@ target_link_libraries(step487_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step488_test tests/step488_test.cpp) +target_include_directories(step488_test PRIVATE src) +target_link_libraries(step488_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/SecurityPreservingTranslation.h b/editor/src/SecurityPreservingTranslation.h new file mode 100644 index 0000000..dc6a23a --- /dev/null +++ b/editor/src/SecurityPreservingTranslation.h @@ -0,0 +1,166 @@ +#pragma once + +// Step 488: Security-Preserving Translation +// Ensures security annotations are preserved or escalated for human review. + +#include +#include +#include +#include + +struct SecuritySourceAnnotation { + std::string key; // e.g. @InputValidation + std::string value; // e.g. sanitized/raw/jwt/aes256 +}; + +struct SecurityTranslationInput { + std::string sourceLanguage; + std::string targetLanguage; + std::vector annotations; +}; + +struct SecurityMappedAnnotation { + std::string sourceKey; + std::string sourceValue; + std::string mappedKey; + std::string mappedValue; + bool preserved = true; + bool reviewRequired = false; + std::string reviewReason; +}; + +struct SecurityTranslationOutput { + std::vector mappings; + bool hasBlockingReview = false; + std::vector notes; + + int preservedCount() const { + int n = 0; + for (const auto& m : mappings) if (m.preserved) ++n; + return n; + } +}; + +class SecurityPreservingTranslation { +public: + static SecurityTranslationOutput translate(const SecurityTranslationInput& in) { + SecurityTranslationOutput out; + const std::string target = lower(in.targetLanguage); + + if (in.annotations.empty()) { + out.notes.push_back("No security annotations found in source"); + return out; + } + + for (const auto& ann : in.annotations) { + auto mapped = mapAnnotation(ann, target); + if (mapped.reviewRequired) out.hasBlockingReview = true; + out.mappings.push_back(std::move(mapped)); + } + + out.notes.push_back("Security annotations preserved through translation"); + if (out.hasBlockingReview) { + out.notes.push_back("One or more annotations require human review"); + } + return out; + } + +private: + static std::string lower(const std::string& s) { + std::string out = s; + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return out; + } + + static bool isInsecureCrypto(const std::string& value) { + std::string v = lower(value); + return v.find("md5") != std::string::npos || v.find("sha1") != std::string::npos; + } + + static SecurityMappedAnnotation mapAnnotation(const SecuritySourceAnnotation& ann, + const std::string& targetLanguage) { + SecurityMappedAnnotation out; + out.sourceKey = ann.key; + out.sourceValue = ann.value; + out.mappedKey = ann.key; + out.mappedValue = ann.value; + + if (ann.key == "@InputValidation") { + if (targetLanguage == "rust") { + out.mappedValue = "whetstone::input_validation(\"" + ann.value + "\")"; + } else if (targetLanguage == "python") { + out.mappedValue = "@input_validation(level=\"" + ann.value + "\")"; + } else if (targetLanguage == "typescript" || targetLanguage == "javascript") { + out.mappedValue = "validateInput(\"" + ann.value + "\")"; + } else { + out.reviewRequired = true; + out.reviewReason = "No deterministic @InputValidation mapping for target language"; + } + return out; + } + + if (ann.key == "@TrustBoundary") { + out.mappedValue = "boundary:" + ann.value; + return out; + } + + if (ann.key == "@Auth") { + if (targetLanguage == "rust") { + out.mappedValue = "AuthGuard::" + ann.value; + } else if (targetLanguage == "python") { + out.mappedValue = "auth_required(method=\"" + ann.value + "\")"; + } else if (targetLanguage == "typescript" || targetLanguage == "javascript") { + out.mappedValue = "@UseAuth(\"" + ann.value + "\")"; + } else { + out.reviewRequired = true; + out.reviewReason = "No deterministic @Auth mapping for target language"; + } + return out; + } + + if (ann.key == "@Encryption") { + if (isInsecureCrypto(ann.value)) { + out.reviewRequired = true; + out.reviewReason = "Insecure crypto algorithm requires human review"; + } + if (targetLanguage == "rust") { + out.mappedValue = "ring::" + ann.value; + } else if (targetLanguage == "python") { + out.mappedValue = "cryptography." + ann.value; + } else if (targetLanguage == "typescript" || targetLanguage == "javascript") { + out.mappedValue = "crypto." + ann.value; + } else { + out.reviewRequired = true; + if (out.reviewReason.empty()) { + out.reviewReason = "No deterministic @Encryption mapping for target language"; + } + } + return out; + } + + if (ann.key == "@Secrets") { + out.mappedValue = "secret_manager(policy=\"" + ann.value + "\")"; + out.reviewRequired = true; + out.reviewReason = "Secrets handling always requires human review"; + return out; + } + + if (ann.key == "@CORS") { + if (targetLanguage == "python") { + out.mappedValue = "configure_cors(\"" + ann.value + "\")"; + } else if (targetLanguage == "typescript" || targetLanguage == "javascript") { + out.mappedValue = "cors({ policy: \"" + ann.value + "\" })"; + } else { + out.reviewRequired = true; + out.reviewReason = "No deterministic @CORS mapping for target language"; + } + return out; + } + + // Unknown security annotation: preserve by passthrough, require review. + out.reviewRequired = true; + out.reviewReason = "Unknown security annotation mapping; preserved for human review"; + return out; + } +}; diff --git a/editor/tests/step488_test.cpp b/editor/tests/step488_test.cpp new file mode 100644 index 0000000..c218547 --- /dev/null +++ b/editor/tests/step488_test.cpp @@ -0,0 +1,147 @@ +// Step 488: Security-Preserving Translation Tests (12 tests) + +#include "SecurityPreservingTranslation.h" + +#include +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static SecurityTranslationOutput run(const std::string& target, + std::initializer_list anns) { + SecurityTranslationInput in; + in.sourceLanguage = "python"; + in.targetLanguage = target; + in.annotations.assign(anns.begin(), anns.end()); + return SecurityPreservingTranslation::translate(in); +} + +void test_input_validation_is_preserved_in_target_language() { + TEST(input_validation_is_preserved_in_target_language); + auto out = run("rust", {{"@InputValidation", "sanitized"}}); + CHECK(out.mappings.size() == 1, "mapping size mismatch"); + CHECK(out.mappings[0].mappedValue.find("input_validation") != std::string::npos, + "input validation mapping missing"); + CHECK(out.mappings[0].preserved, "annotation should be preserved"); + PASS(); +} + +void test_trust_boundary_is_maintained() { + TEST(trust_boundary_is_maintained); + auto out = run("typescript", {{"@TrustBoundary", "external-api"}}); + CHECK(out.mappings[0].mappedValue == "boundary:external-api", "trust boundary mapping mismatch"); + PASS(); +} + +void test_auth_annotation_maps_to_target_auth_pattern() { + TEST(auth_annotation_maps_to_target_auth_pattern); + auto out = run("python", {{"@Auth", "jwt"}}); + CHECK(out.mappings[0].mappedValue.find("auth_required") != std::string::npos, + "auth mapping missing"); + PASS(); +} + +void test_encryption_annotation_maps_to_target_crypto_library() { + TEST(encryption_annotation_maps_to_target_crypto_library); + auto out = run("rust", {{"@Encryption", "aes256"}}); + CHECK(out.mappings[0].mappedValue.find("ring::aes256") != std::string::npos, + "encryption mapping missing"); + PASS(); +} + +void test_unknown_security_annotation_triggers_review_required() { + TEST(unknown_security_annotation_triggers_review_required); + auto out = run("python", {{"@SecurityCustom", "foo"}}); + CHECK(out.mappings[0].reviewRequired, "expected review required"); + CHECK(out.hasBlockingReview, "blocking review should be set"); + PASS(); +} + +void test_empty_annotation_list_returns_empty_with_note() { + TEST(empty_annotation_list_returns_empty_with_note); + auto out = run("python", {}); + CHECK(out.mappings.empty(), "expected empty mappings"); + CHECK(!out.notes.empty(), "expected explanatory note"); + PASS(); +} + +void test_insecure_crypto_algorithm_requires_human_review() { + TEST(insecure_crypto_algorithm_requires_human_review); + auto out = run("python", {{"@Encryption", "md5"}}); + CHECK(out.mappings[0].reviewRequired, "expected review for insecure crypto"); + CHECK(out.mappings[0].reviewReason.find("Insecure crypto") != std::string::npos, + "missing insecure crypto reason"); + PASS(); +} + +void test_unmappable_target_language_requires_review_without_dropping_annotation() { + TEST(unmappable_target_language_requires_review_without_dropping_annotation); + auto out = run("haskell", {{"@InputValidation", "raw"}}); + CHECK(out.mappings[0].reviewRequired, "expected review required"); + CHECK(out.mappings[0].preserved, "must preserve annotation"); + CHECK(out.preservedCount() == 1, "annotation should remain present"); + PASS(); +} + +void test_security_annotations_never_auto_removed_count_matches_source() { + TEST(security_annotations_never_auto_removed_count_matches_source); + auto out = run("rust", { + {"@InputValidation", "raw"}, + {"@TrustBoundary", "db"}, + {"@Auth", "session"}, + {"@Encryption", "aes256"} + }); + CHECK(out.mappings.size() == 4, "all source annotations should be mapped"); + CHECK(out.preservedCount() == 4, "all annotations must remain preserved"); + PASS(); +} + +void test_blocking_review_flag_only_when_required() { + TEST(blocking_review_flag_only_when_required); + auto ok = run("rust", {{"@InputValidation", "sanitized"}, {"@Auth", "jwt"}}); + auto bad = run("rust", {{"@Secrets", "rotation:30d"}}); + CHECK(!ok.hasBlockingReview, "non-problematic set should not block"); + CHECK(bad.hasBlockingReview, "secrets set should block"); + PASS(); +} + +void test_cors_annotation_maps_for_supported_targets() { + TEST(cors_annotation_maps_for_supported_targets); + auto out = run("typescript", {{"@CORS", "public-read"}}); + CHECK(out.mappings[0].mappedValue.find("cors(") != std::string::npos, "missing cors mapping"); + PASS(); +} + +void test_secrets_annotation_always_requires_review() { + TEST(secrets_annotation_always_requires_review); + auto out = run("python", {{"@Secrets", "rotation:7d"}}); + CHECK(out.mappings[0].reviewRequired, "secrets must require review"); + CHECK(out.mappings[0].mappedValue.find("secret_manager") != std::string::npos, + "expected secret manager mapping"); + PASS(); +} + +int main() { + std::cout << "Step 488: Security-Preserving Translation Tests\n"; + + test_input_validation_is_preserved_in_target_language(); // 1 + test_trust_boundary_is_maintained(); // 2 + test_auth_annotation_maps_to_target_auth_pattern(); // 3 + test_encryption_annotation_maps_to_target_crypto_library(); // 4 + test_unknown_security_annotation_triggers_review_required(); // 5 + test_empty_annotation_list_returns_empty_with_note(); // 6 + test_insecure_crypto_algorithm_requires_human_review(); // 7 + test_unmappable_target_language_requires_review_without_dropping_annotation(); // 8 + test_security_annotations_never_auto_removed_count_matches_source(); // 9 + test_blocking_review_flag_only_when_required(); // 10 + test_cors_annotation_maps_for_supported_targets(); // 11 + test_secrets_annotation_always_requires_review(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 195ef17..e8d9ade 100644 --- a/progress.md +++ b/progress.md @@ -7139,3 +7139,43 @@ output validation on known-good and deliberately-broken step artifacts. review -> templates/scaffolding -> build/dependency awareness -> multi-language orchestration -> step spec/context/convention/test-plan synthesis -> dispatch/validation -> phase-level integration validation. + +### Step 488: Security-Preserving Translation +**Status:** PASS (12/12 tests) + +Starts Sprint 24 Phase 24b by adding a security-preserving annotation +translation layer so security metadata is carried into target-language +representations and never silently dropped during transpilation. + +**Files added:** +- `editor/src/SecurityPreservingTranslation.h` — security annotation mapper: + - `SecurityTranslationInput`, `SecuritySourceAnnotation` + - `SecurityMappedAnnotation`, `SecurityTranslationOutput` + - `translate(...)` mapping behavior: + - `@InputValidation` mapped to target-language validation forms + - `@TrustBoundary` preserved across boundaries + - `@Auth` mapped to target auth patterns + - `@Encryption` mapped to target crypto library forms + - `@CORS` mapped for supported targets + - `@Secrets` preserved but always `reviewRequired` + - strict guardrails: + - security annotations are always preserved + - unknown/unmappable security annotations trigger human review + - insecure crypto values (`md5`, `sha1`) force review +- `editor/tests/step488_test.cpp` — 12 tests covering: + - per-annotation mapping behavior + - unsupported-language review escalation + - no-annotation edge case + - preservation-count invariants + - blocking-review flag semantics +- `editor/CMakeLists.txt` — `step488_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step488_test step487_test` — PASS +- `./editor/build-native/step488_test` — PASS (12/12) +- `./editor/build-native/step487_test` — PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/SecurityPreservingTranslation.h` within header-size limit (`166` <= `600`) +- `editor/tests/step488_test.cpp` within test-file size guidance (`147` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`