Complete Step 488: security-preserving translation
This commit is contained in:
@@ -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)
|
||||
|
||||
166
editor/src/SecurityPreservingTranslation.h
Normal file
166
editor/src/SecurityPreservingTranslation.h
Normal file
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
// Step 488: Security-Preserving Translation
|
||||
// Ensures security annotations are preserved or escalated for human review.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<SecuritySourceAnnotation> 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<SecurityMappedAnnotation> mappings;
|
||||
bool hasBlockingReview = false;
|
||||
std::vector<std::string> 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<char>(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;
|
||||
}
|
||||
};
|
||||
147
editor/tests/step488_test.cpp
Normal file
147
editor/tests/step488_test.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
// Step 488: Security-Preserving Translation Tests (12 tests)
|
||||
|
||||
#include "SecurityPreservingTranslation.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
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<SecuritySourceAnnotation> 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;
|
||||
}
|
||||
40
progress.md
40
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`
|
||||
|
||||
Reference in New Issue
Block a user