Complete Step 489: secure by default generator

This commit is contained in:
Bill
2026-02-16 21:52:23 -07:00
parent a76e49ada2
commit 9d42629a16
4 changed files with 419 additions and 0 deletions

View File

@@ -3280,4 +3280,13 @@ target_link_libraries(step488_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step489_test tests/step489_test.cpp)
target_include_directories(step489_test PRIVATE src)
target_link_libraries(step489_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)

View File

@@ -0,0 +1,202 @@
#pragma once
// Step 489: Secure-by-Default Code Generation
// Emits secure templates and defaults for common risky operations.
#include <algorithm>
#include <cctype>
#include <string>
#include <vector>
enum class SecureSnippetKind {
SqlQuery,
InputHandling,
FileOperation,
NetworkCall,
CryptoOperation
};
struct SecureGenerationRequest {
std::string language;
SecureSnippetKind kind = SecureSnippetKind::InputHandling;
std::string operationName;
std::string securityRequirement;
};
struct SecureGenerationOutput {
std::string code;
std::vector<std::string> annotations;
std::vector<std::string> notes;
bool requiresHumanReview = false;
};
class SecureByDefaultGenerator {
public:
static SecureGenerationOutput generate(const SecureGenerationRequest& req) {
SecureGenerationOutput out;
const std::string lang = lower(req.language);
out.annotations.push_back("@Risk(security)");
out.annotations.push_back("@Review(required, human)");
if (!req.securityRequirement.empty()) {
out.annotations.push_back("@SecurityRequirement(" + req.securityRequirement + ")");
}
switch (req.kind) {
case SecureSnippetKind::SqlQuery:
out.code = generateSql(lang, req.operationName);
out.notes.push_back("Uses parameterized query placeholders");
break;
case SecureSnippetKind::InputHandling:
out.code = generateInputHandling(lang, req.operationName);
out.annotations.push_back("@InputValidation(raw)");
out.notes.push_back("Input marked raw until explicit sanitization");
break;
case SecureSnippetKind::FileOperation:
out.code = generateFileOperation(lang, req.operationName);
out.notes.push_back("Path traversal checks included");
break;
case SecureSnippetKind::NetworkCall:
out.code = generateNetworkCall(lang, req.operationName);
out.notes.push_back("Timeout and TLS verification included");
break;
case SecureSnippetKind::CryptoOperation:
out.code = generateCrypto(lang, req.operationName, out.requiresHumanReview);
out.annotations.push_back("@Automatability(human)");
out.notes.push_back("Crypto generated with modern algorithm defaults");
break;
}
if (contains(lower(out.code), "md5") || contains(lower(out.code), "sha1")) {
out.requiresHumanReview = true;
out.notes.push_back("Weak crypto keyword detected; escalation required");
}
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 contains(const std::string& text, const std::string& needle) {
return text.find(needle) != std::string::npos;
}
static std::string opName(const std::string& n, const std::string& fallback) {
return n.empty() ? fallback : n;
}
static std::string generateSql(const std::string& lang, const std::string& op) {
const std::string f = opName(op, "fetchRecord");
if (lang == "python") {
return "def " + f + "(conn, user_id):\n"
" query = \"SELECT * FROM users WHERE id = %s\"\n"
" return conn.execute(query, (user_id,)).fetchone()\n";
}
if (lang == "typescript" || lang == "javascript") {
return "export async function " + f + "(db, userId) {\n"
" return db.query(\"SELECT * FROM users WHERE id = ?\", [userId]);\n"
"}\n";
}
if (lang == "rust") {
return "pub async fn " + f + "(pool: &DbPool, user_id: i64) -> Result<Row, Error> {\n"
" sqlx::query(\"SELECT * FROM users WHERE id = $1\").bind(user_id).fetch_one(pool).await\n"
"}\n";
}
return "STUB: use parameterized query for " + f + "\n";
}
static std::string generateInputHandling(const std::string& lang, const std::string& op) {
const std::string f = opName(op, "handleInput");
if (lang == "python") {
return "def " + f + "(raw_input: str):\n"
" # raw until sanitized\n"
" return raw_input\n";
}
if (lang == "typescript" || lang == "javascript") {
return "export function " + f + "(rawInput: string) {\n"
" // raw until sanitized\n"
" return rawInput;\n"
"}\n";
}
if (lang == "rust") {
return "pub fn " + f + "(raw_input: &str) -> &str {\n"
" raw_input\n"
"}\n";
}
return "STUB: treat input as raw until sanitized in " + f + "\n";
}
static std::string generateFileOperation(const std::string& lang, const std::string& op) {
const std::string f = opName(op, "readFileSafe");
if (lang == "python") {
return "def " + f + "(base_dir, candidate):\n"
" import os\n"
" resolved = os.path.realpath(os.path.join(base_dir, candidate))\n"
" if not resolved.startswith(os.path.realpath(base_dir)):\n"
" raise ValueError(\"path traversal blocked\")\n"
" with open(resolved, \"r\", encoding=\"utf-8\") as fh:\n"
" return fh.read()\n";
}
if (lang == "typescript" || lang == "javascript") {
return "export function " + f + "(baseDir, candidate) {\n"
" const path = require(\"path\");\n"
" const resolved = path.resolve(baseDir, candidate);\n"
" if (!resolved.startsWith(path.resolve(baseDir))) throw new Error(\"path traversal blocked\");\n"
" return require(\"fs\").readFileSync(resolved, \"utf8\");\n"
"}\n";
}
return "STUB: enforce canonical path prefix check in " + f + "\n";
}
static std::string generateNetworkCall(const std::string& lang, const std::string& op) {
const std::string f = opName(op, "fetchSecure");
if (lang == "python") {
return "def " + f + "(url):\n"
" import requests\n"
" return requests.get(url, timeout=5, verify=True)\n";
}
if (lang == "typescript" || lang == "javascript") {
return "export async function " + f + "(url) {\n"
" const controller = new AbortController();\n"
" setTimeout(() => controller.abort(), 5000);\n"
" return fetch(url, { signal: controller.signal });\n"
"}\n";
}
if (lang == "rust") {
return "pub async fn " + f + "(url: &str) -> Result<Response, reqwest::Error> {\n"
" reqwest::Client::builder().use_rustls_tls().timeout(std::time::Duration::from_secs(5)).build()?.get(url).send().await\n"
"}\n";
}
return "STUB: include timeout and TLS verification in " + f + "\n";
}
static std::string generateCrypto(const std::string& lang, const std::string& op,
bool& requiresHumanReview) {
requiresHumanReview = true;
const std::string f = opName(op, "encryptData");
if (lang == "python") {
return "def " + f + "(plaintext: bytes, key: bytes):\n"
" from cryptography.hazmat.primitives.ciphers.aead import AESGCM\n"
" nonce = b\"000000000000\"\n"
" return AESGCM(key).encrypt(nonce, plaintext, None)\n";
}
if (lang == "typescript" || lang == "javascript") {
return "export function " + f + "(plaintext, key, iv) {\n"
" const crypto = require(\"crypto\");\n"
" const cipher = crypto.createCipheriv(\"aes-256-gcm\", key, iv);\n"
" return Buffer.concat([cipher.update(plaintext), cipher.final()]);\n"
"}\n";
}
if (lang == "rust") {
return "pub fn " + f + "(plaintext: &[u8]) {\n"
" // STUB: use AES-GCM via audited crate; keep human review in loop\n"
"}\n";
}
return "STUB: use modern AEAD algorithm (AES-GCM/ChaCha20-Poly1305) in " + f + "\n";
}
};

View File

@@ -0,0 +1,164 @@
// Step 489: Secure-by-Default Code Generation Tests (12 tests)
#include "SecureByDefaultGenerator.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 SecureGenerationOutput run(const std::string& lang, SecureSnippetKind kind,
const std::string& op = "") {
SecureGenerationRequest r;
r.language = lang;
r.kind = kind;
r.operationName = op;
r.securityRequirement = "default";
return SecureByDefaultGenerator::generate(r);
}
void test_sql_generation_uses_parameterized_queries_not_concat() {
TEST(sql_generation_uses_parameterized_queries_not_concat);
auto out = run("python", SecureSnippetKind::SqlQuery, "getUser");
CHECK(out.code.find("%s") != std::string::npos, "expected parameter placeholder");
CHECK(out.code.find("+") == std::string::npos, "should not use string concatenation");
PASS();
}
void test_input_handling_marks_raw_until_sanitized() {
TEST(input_handling_marks_raw_until_sanitized);
auto out = run("typescript", SecureSnippetKind::InputHandling, "processInput");
bool foundRaw = false;
for (const auto& a : out.annotations) {
if (a == "@InputValidation(raw)") foundRaw = true;
}
CHECK(foundRaw, "expected @InputValidation(raw)");
CHECK(out.code.find("raw until sanitized") != std::string::npos, "missing raw comment");
PASS();
}
void test_file_operations_include_path_traversal_guard() {
TEST(file_operations_include_path_traversal_guard);
auto out = run("python", SecureSnippetKind::FileOperation, "readSafe");
CHECK(out.code.find("realpath") != std::string::npos, "missing canonicalization");
CHECK(out.code.find("path traversal blocked") != std::string::npos, "missing traversal check");
PASS();
}
void test_network_calls_include_timeout_and_tls_verification() {
TEST(network_calls_include_timeout_and_tls_verification);
auto out = run("python", SecureSnippetKind::NetworkCall, "fetchSecure");
CHECK(out.code.find("timeout=5") != std::string::npos, "missing timeout");
CHECK(out.code.find("verify=True") != std::string::npos, "missing TLS verify");
PASS();
}
void test_crypto_generation_avoids_md5_and_sha1_defaults() {
TEST(crypto_generation_avoids_md5_and_sha1_defaults);
auto out = run("python", SecureSnippetKind::CryptoOperation, "encrypt");
CHECK(out.code.find("MD5") == std::string::npos, "must not include md5");
CHECK(out.code.find("SHA1") == std::string::npos, "must not include sha1");
CHECK(out.code.find("AESGCM") != std::string::npos, "expected modern crypto");
PASS();
}
void test_crypto_generation_is_marked_human_review_only() {
TEST(crypto_generation_is_marked_human_review_only);
auto out = run("rust", SecureSnippetKind::CryptoOperation, "encryptBlob");
CHECK(out.requiresHumanReview, "crypto should require human review");
bool hasAutom = false;
for (const auto& a : out.annotations) if (a == "@Automatability(human)") hasAutom = true;
CHECK(hasAutom, "missing @Automatability(human)");
PASS();
}
void test_all_generated_snippets_include_security_risk_and_review_annotations() {
TEST(all_generated_snippets_include_security_risk_and_review_annotations);
auto a = run("python", SecureSnippetKind::SqlQuery);
auto b = run("python", SecureSnippetKind::InputHandling);
auto c = run("python", SecureSnippetKind::FileOperation);
auto d = run("python", SecureSnippetKind::NetworkCall);
auto e = run("python", SecureSnippetKind::CryptoOperation);
auto hasBase = [](const SecureGenerationOutput& o) {
bool risk = false, review = false;
for (const auto& ann : o.annotations) {
if (ann == "@Risk(security)") risk = true;
if (ann == "@Review(required, human)") review = true;
}
return risk && review;
};
CHECK(hasBase(a) && hasBase(b) && hasBase(c) && hasBase(d) && hasBase(e),
"missing base security annotations");
PASS();
}
void test_sql_generation_for_rust_uses_bind_pattern() {
TEST(sql_generation_for_rust_uses_bind_pattern);
auto out = run("rust", SecureSnippetKind::SqlQuery, "fetchOne");
CHECK(out.code.find(".bind(") != std::string::npos, "expected bind usage");
PASS();
}
void test_network_generation_for_rust_uses_timeout_and_rustls() {
TEST(network_generation_for_rust_uses_timeout_and_rustls);
auto out = run("rust", SecureSnippetKind::NetworkCall, "fetch");
CHECK(out.code.find("use_rustls_tls") != std::string::npos, "missing rustls");
CHECK(out.code.find("from_secs(5)") != std::string::npos, "missing timeout");
PASS();
}
void test_file_operation_for_unknown_lang_falls_back_to_secure_stub() {
TEST(file_operation_for_unknown_lang_falls_back_to_secure_stub);
auto out = run("lua", SecureSnippetKind::FileOperation, "readSafe");
CHECK(out.code.find("canonical path prefix check") != std::string::npos,
"missing secure fallback guidance");
PASS();
}
void test_operation_name_is_respected_in_generated_code() {
TEST(operation_name_is_respected_in_generated_code);
auto out = run("typescript", SecureSnippetKind::InputHandling, "sanitizeLater");
CHECK(out.code.find("sanitizeLater") != std::string::npos, "operation name missing");
PASS();
}
void test_security_requirement_annotation_is_emitted() {
TEST(security_requirement_annotation_is_emitted);
SecureGenerationRequest r;
r.language = "python";
r.kind = SecureSnippetKind::SqlQuery;
r.operationName = "query";
r.securityRequirement = "pci-dss";
auto out = SecureByDefaultGenerator::generate(r);
bool found = false;
for (const auto& a : out.annotations) {
if (a == "@SecurityRequirement(pci-dss)") found = true;
}
CHECK(found, "missing security requirement annotation");
PASS();
}
int main() {
std::cout << "Step 489: Secure-by-Default Code Generation Tests\n";
test_sql_generation_uses_parameterized_queries_not_concat(); // 1
test_input_handling_marks_raw_until_sanitized(); // 2
test_file_operations_include_path_traversal_guard(); // 3
test_network_calls_include_timeout_and_tls_verification(); // 4
test_crypto_generation_avoids_md5_and_sha1_defaults(); // 5
test_crypto_generation_is_marked_human_review_only(); // 6
test_all_generated_snippets_include_security_risk_and_review_annotations(); // 7
test_sql_generation_for_rust_uses_bind_pattern(); // 8
test_network_generation_for_rust_uses_timeout_and_rustls(); // 9
test_file_operation_for_unknown_lang_falls_back_to_secure_stub(); // 10
test_operation_name_is_respected_in_generated_code(); // 11
test_security_requirement_annotation_is_emitted(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed)
<< " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -7179,3 +7179,47 @@ representations and never silently dropped during transpilation.
- `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`
### Step 489: Secure-by-Default Code Generation
**Status:** PASS (12/12 tests)
Implements secure-by-default snippet generation for high-risk operation
classes so generated code starts from safe patterns instead of relying on
post-hoc hardening.
**Files added:**
- `editor/src/SecureByDefaultGenerator.h` — secure generation engine:
- `SecureGenerationRequest`, `SecureGenerationOutput`, `SecureSnippetKind`
- snippet generators for:
- SQL queries (parameterized placeholders / bind APIs)
- input handling (`@InputValidation(raw)` baseline)
- file operations (path traversal guards)
- network calls (timeouts + TLS verification)
- crypto operations (modern defaults + mandatory human review)
- baseline annotation emission:
- `@Risk(security)`
- `@Review(required, human)`
- optional `@SecurityRequirement(...)`
- `@Automatability(human)` for crypto
- weak-crypto guard (`md5`/`sha1`) escalation path
- `editor/tests/step489_test.cpp` — 12 tests covering:
- parameterized SQL enforcement
- raw-input annotation semantics
- path traversal checks
- timeout/TLS requirements
- crypto hardening defaults and human-review gating
- base security annotation coverage across snippet kinds
- language-specific secure path checks (Rust SQL/network)
- secure fallback behavior for unknown languages
- operation-name propagation and security requirement annotation
- `editor/CMakeLists.txt``step489_test` target
**Verification run:**
- `cmake --build editor/build-native --target step489_test step488_test` — PASS
- `./editor/build-native/step489_test` — PASS (12/12)
- `./editor/build-native/step488_test` — PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/SecureByDefaultGenerator.h` within header-size limit (`202` <= `600`)
- `editor/tests/step489_test.cpp` within test-file size guidance (`164` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`