Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions crates/starknet_transaction_prover/resources/test_tls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Test TLS Material

**This directory contains a self-signed certificate and its private key used
exclusively by the unit tests in `src/server/tls_test.rs`.**

The key is intentionally checked into the repository — it is a test fixture,
not a secret. Do not reuse this material outside of test code.

## Properties

- Subject: `CN=localhost`
- SAN: `DNS:localhost,IP:127.0.0.1`
- Validity: 100 years from generation
- Key: 2048-bit RSA, unencrypted

## Regenerating

```bash
openssl req -x509 -newkey rsa:2048 \
-keyout crates/starknet_transaction_prover/resources/test_tls/key.pem \
-out crates/starknet_transaction_prover/resources/test_tls/cert.pem \
-sha256 -days 36500 -nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
```
19 changes: 19 additions & 0 deletions crates/starknet_transaction_prover/resources/test_tls/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDJzCCAg+gAwIBAgIUCfCD8/3lfYaThN2hDz8c4CIbTDowDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MDUxNzE1Mjk0N1oYDzIxMjYw
NDIzMTUyOTQ3WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCcDEcAhUbdyBnUkIGykVCvNtXVg+sHYlvTTYnpasxM
iUyY9jyoLYJlBPZjmNPipDd67REL2fGa7q3hkkkHAy4BxdYIqE2l9fPkX2unY/nS
OWDemwPgOXEorcDWJC/kIVwNtVdqfLKG22d88QUvMe4rqUCA6J32dQv1/UAQ9OKw
9kVQ3NAwGTMG2e61A3Ueur0DBmtap5YSn7IlT3HKQXSkTX08V7MCf8W9N3/Bg01h
a7yAtNiQX66Gs4y/8CsM7eTBnLt4e0MVyhSJ9Zj2Ibpatr/YyWAhaW8p7y4sGo50
3ovQ6isrOO8ayi6d7fYVCI27NDoiSmJ67okfQk0rf3DJAgMBAAGjbzBtMB0GA1Ud
DgQWBBQdz7tV14UJxD6RchjKEDz61wgN/zAfBgNVHSMEGDAWgBQdz7tV14UJxD6R
chjKEDz61wgN/zAPBgNVHRMBAf8EBTADAQH/MBoGA1UdEQQTMBGCCWxvY2FsaG9z
dIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAIiAb1/7gA/NXMck/k3WGnYCp3Aut
rYDbfq1fwG/GJZ572qd9XREtuC198QPpQ04yjv7v2sspeWFqAS8e+Gr67h1cQ7q/
mEnGPXQBKQ9r4TSamsFHnrh5x/J9Ec/hBXU9xd8OemZ+o00Itxn1FWwDBvudzfOA
1IJ/7RNxmhUK2/dOOS9Jo5zGhRX/f6s8qmW6ZX2dRpvio3YqV30LpVSBZMsfQiWv
dRHcl5xVoxxtvSkKt1Ou8VE2SEl61c7+Gb3zPBxOdT59QDFGZvX9PC8fPsOq2dNh
ZhYJ4UJfWP5lMa372GATdPjVlZSf575xDpagUgSEKxEyCd7XC2k/p00u+w==
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions crates/starknet_transaction_prover/resources/test_tls/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcDEcAhUbdyBnU
Comment on lines +1 to +2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:

Hard-coded private key in source code allows attackers who gain repository access to impersonate your service or decrypt sensitive data.

More details about this

This code contains a hard-coded private key embedded directly in your source code. An attacker who gains access to your repository—whether through a compromised developer account, leaked source code, or cloned Git history—would immediately obtain this private key.

Here's a concrete attack scenario:

  1. Attacker gains repository access: A developer's GitHub account is compromised, or the repo is accidentally made public, exposing the code containing the -----BEGIN PRIVATE KEY----- block.

  2. Attacker extracts the key: The attacker copies the entire private key (the base64-encoded $KEY content between the BEGIN/END markers).

  3. Attacker impersonates your service: Using this private key, the attacker can:

    • Sign JWTs or other tokens to forge authentication credentials
    • Decrypt data encrypted with the corresponding public key
    • Establish TLS connections pretending to be your service
    • Access any system that trusts this key for authentication
  4. Lateral movement: If this key is used to authenticate to cloud services, databases, or internal APIs, the attacker now has a foothold into your infrastructure.

The danger is that this exposure is permanent—even if you delete the key from your repo now, it remains in Git history and anyone with repository access can retrieve it.

To resolve this comment:

✨ Commit fix suggestion
  1. Remove the entire private key block from your codebase, including everything from -----BEGIN PRIVATE KEY----- to -----END PRIVATE KEY-----.
  2. Store the private key in a secure location outside your source code, such as a private file on the server, a secure secrets manager, or environment variable.
  3. Update your application to load the private key at runtime from the secure location instead of from the code. For example, use fs.readFileSync('/path/to/private.key') in Node.js, or reference an environment variable that points to the file path.
  4. Add the filename or pattern for private keys (such as *.key or the specific key file) to your .gitignore file to prevent accidental commits in the future.
💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by detected-private-key.

You can view more details about this finding in the Semgrep AppSec Platform.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/fp Test fixture only. Cert subject is CN=localhost (SAN: DNS:localhost,IP:127.0.0.1); used exclusively by src/server/tls_test.rs to exercise start_tls_server and load_tls_acceptor. Provenance and regeneration command documented in resources/test_tls/README.md. The key is not referenced from any production code path.

kIGykVCvNtXVg+sHYlvTTYnpasxMiUyY9jyoLYJlBPZjmNPipDd67REL2fGa7q3h
kkkHAy4BxdYIqE2l9fPkX2unY/nSOWDemwPgOXEorcDWJC/kIVwNtVdqfLKG22d8
8QUvMe4rqUCA6J32dQv1/UAQ9OKw9kVQ3NAwGTMG2e61A3Ueur0DBmtap5YSn7Il
T3HKQXSkTX08V7MCf8W9N3/Bg01ha7yAtNiQX66Gs4y/8CsM7eTBnLt4e0MVyhSJ
9Zj2Ibpatr/YyWAhaW8p7y4sGo503ovQ6isrOO8ayi6d7fYVCI27NDoiSmJ67okf
Qk0rf3DJAgMBAAECggEAQmpDSehvifMhc0Pxv4NzmK85AX/85w6o0F0fBlZrD2Qc
UrnyhQ2hgsdC6o7gF4UXC92cNLQUzYEqRmhRZoem7CA8gUDIk4sDu74U/pBhgmTj
YrsNQkCQdeTFvx51t52vJTJ6OxtJjHYTLK0ULMsOeEy35GWc3Ylhhte7jbv8Q55S
wSwmCqxnEj6+5HlAeOIC2LJnrerDEejlTpXH1HCEsTH0dd4EQ/sHixzNVt2MSHDU
fjhsmTl6kTO+RcIPoohuUSMrtrN9j/T4GxcrZqqwdzws/KMg1GYWvRxBJQHuO1yl
5ivhA4sjE8cmN2SbcgrYa3R7aK3XzrCmxx4DKHNYlQKBgQDWeS4BIVEIKwzjjQOC
RPhPg4Zo+0xYzceq4Q7ERn6mBojsLxNF91Z0Yx32rDpQziV1FESe0xmWneRCIjP8
Ua31MU5FeH1GI7q0RyWD7XoUTI8io8cFdMiQEbRrYqARVrHun6NpBHIUG5Z5az8L
JWRP/P0QN90cgkgLnqwxFicHywKBgQC6Qx7ej5cPBBf/m8Aijmd8cUDqrmk6A4Zc
df8IjsfMdkv7H29qBip4KgNuThREaTq5fuCqgKvVKUgmudEFousfuWjzJsICBE9h
4DGFDUxPBYyUFf70PmlQ8e4avvNg1Cx5VgIt4M2IAsUmiAC/52y14R67u7MfQP7q
UWU3YitPOwKBgQCkq/A9n+YGnn9L68aI7Am3i2XVDzXEbWNj+V8MJpAxS40vwslK
jCjOPhgQgJZZ2p358fDp/W2FLn/Go1pE3jXxr8TIJEYTZ3V/26ybSefU1B+GWjeC
IfOoYl+jn9sE1QrTC7E8/dPVSoVTfpuuJCyMGdP38tyLeiB1A4R0P+0B1wKBgAtZ
79Wsdo5Jt5SyT0FL4G6rEEO9IViRwmx8HHDPEsoZI4RIZCfX/FqaZN8iDwYkS5nm
a5a4hMBW5bjGdkCbryydxhGbeRNaY+QZH6t2JgJi2jBkLsd/zjdKpzImFPr/sz4p
ybQ2ERCK6qzweOs5FVz4PUE/rSjocyCgmUSIzQ7lAoGAE+Vc2EeHPxFRZgVSFS6m
SEl5p5h8Wfr39+HKBSVd1P7QMHAFN11yXrGhIrRnAkI37chSvNtwLbTLicHuY127
zEVAVXATvTZosErLp8WuNCxnb/1PxBVv1RMxJHm0ibjfRuWpDF+Gstn6ESQYKyPU
Z84XK248kL9fShkmRNRujGI=
-----END PRIVATE KEY-----
2 changes: 2 additions & 0 deletions crates/starknet_transaction_prover/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub use health::{HealthLayer, HEALTH_PATH};
mod cors_test;
#[cfg(test)]
mod rpc_spec_test;
#[cfg(test)]
mod tls_test;

#[cfg(test)]
mod request_body_size_test;
Expand Down
2 changes: 1 addition & 1 deletion crates/starknet_transaction_prover/src/server/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ pub async fn start_tls_server(
}

/// Loads a certificate chain and private key from PEM files and builds a TLS acceptor.
fn load_tls_acceptor(cert_path: &Path, key_path: &Path) -> anyhow::Result<TlsAcceptor> {
pub(crate) fn load_tls_acceptor(cert_path: &Path, key_path: &Path) -> anyhow::Result<TlsAcceptor> {
let cert_pem = std::fs::read(cert_path)
.with_context(|| format!("Failed to read TLS certificate file: {}", cert_path.display()))?;
let cert_chain: Vec<CertificateDer<'static>> =
Expand Down
148 changes: 148 additions & 0 deletions crates/starknet_transaction_prover/src/server/tls_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//! Integration tests for the TLS server bootstrap.
//!
//! Uses a self-signed certificate checked into `resources/test_tls/` (CN=localhost, valid for
//! 100 years). See `resources/test_tls/README.md` for the openssl regeneration command.

use std::io::Write;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};

use rstest::rstest;
use serde_json::Value;
use tempfile::NamedTempFile;

use crate::server::mock_rpc::MockProvingRpc;
use crate::server::rpc_api::ProvingRpcServer;
use crate::server::rpc_impl::SPEC_VERSION;
use crate::server::tls::{load_tls_acceptor, start_tls_server};

fn ensure_crypto_provider() {
let _ = tokio_rustls::rustls::crypto::aws_lc_rs::default_provider().install_default();
}

fn test_cert_path() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("resources/test_tls/cert.pem")
}

fn test_key_path() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("resources/test_tls/key.pem")
}

fn write_pem_to_tempfile(pem_bytes: &[u8]) -> NamedTempFile {
let mut file = NamedTempFile::new().unwrap();
file.write_all(pem_bytes).unwrap();
file.flush().unwrap();
file
}

fn spec_version_request() -> Value {
serde_json::json!({
"jsonrpc": "2.0",
"id": "1",
"method": "starknet_specVersion"
})
}

async fn start_test_tls_server() -> (SocketAddr, jsonrpsee::server::ServerHandle, Vec<u8>) {
let methods = MockProvingRpc::from_expected_json().into_rpc();
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();

let (local_addr, handle) = start_tls_server(
addr,
&test_cert_path(),
&test_key_path(),
methods,
10, // max_connections
5 * 1024 * 1024, // max_request_body_size
None, // cors_layer
None, // ohttp_layer
)
.await
.expect("Failed to start TLS server");

let cert_pem = std::fs::read(test_cert_path()).unwrap();
(local_addr, handle, cert_pem)
}

#[tokio::test]
async fn test_https_spec_version_succeeds() {
ensure_crypto_provider();
let (addr, handle, cert_pem) = start_test_tls_server().await;

let cert = reqwest::tls::Certificate::from_pem(&cert_pem).unwrap();
let client = reqwest::Client::builder().add_root_certificate(cert).build().unwrap();

let response = client
.post(format!("https://localhost:{}", addr.port()))
.json(&spec_version_request())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client URL mismatches bind address

Medium Severity

The TLS server is bound to 127.0.0.1, but the integration tests post to https://localhost:… and http://localhost:…. On hosts where localhost resolves to ::1 first, nothing listens on that address, so the HTTPS case can fail flakily and the HTTP negative case may error with connection refused instead of exercising plain HTTP against a TLS listener.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 02903ed. Configure here.

.send()
.await
.expect("HTTPS request failed");

assert_eq!(response.status(), 200);
let json: Value = response.json().await.unwrap();
assert_eq!(json["result"].as_str().unwrap(), SPEC_VERSION);

handle.stop().unwrap();
}

#[tokio::test]
async fn test_http_to_tls_server_fails() {
ensure_crypto_provider();
let (addr, handle, _cert_pem) = start_test_tls_server().await;

// Plain HTTP to a TLS server should fail (connection or protocol error).
let result = reqwest::Client::new()
.post(format!("http://localhost:{}", addr.port()))
.json(&spec_version_request())
.send()
.await;
assert!(result.is_err(), "Expected HTTP to TLS server to fail, got: {result:?}");

handle.stop().unwrap();
}
Comment thread
cursor[bot] marked this conversation as resolved.

/// How a given path argument is materialised for `load_tls_acceptor`.
enum PathMode {
/// Use the checked-in valid test fixture.
Valid,
/// Path to a file that does not exist.
Missing,
/// Path to a tempfile containing these bytes (returned alongside the path so the
/// `NamedTempFile` is kept alive for the call).
Junk(&'static [u8]),
}

/// `PathMode::Junk` returns `Some(tempfile)` so the tempfile is dropped after the test, not before.
fn materialise(mode: PathMode, missing: &str, valid: PathBuf) -> (PathBuf, Option<NamedTempFile>) {
match mode {
PathMode::Valid => (valid, None),
PathMode::Missing => (missing.into(), None),
PathMode::Junk(bytes) => {
let file = write_pem_to_tempfile(bytes);
(file.path().into(), Some(file))
}
}
}

/// Each case isolates one specific failure path by holding the other input valid, so a green test
/// proves `load_tls_acceptor` actually rejected on the named reason and not on something earlier.
#[rstest]
#[case::missing_cert(PathMode::Missing, PathMode::Valid)]
#[case::missing_key(PathMode::Valid, PathMode::Missing)]
#[case::invalid_cert_pem(PathMode::Junk(b"not a valid PEM cert"), PathMode::Valid)]
#[case::invalid_key_pem(PathMode::Valid, PathMode::Junk(b"not a valid PEM key"))]
fn test_load_tls_acceptor_failure(#[case] cert: PathMode, #[case] key: PathMode) {
let (cert_path, _cert_tmp) = materialise(cert, "/nonexistent/cert.pem", test_cert_path());
let (key_path, _key_tmp) = materialise(key, "/nonexistent/key.pem", test_key_path());

assert!(load_tls_acceptor(&cert_path, &key_path).is_err());
}

#[test]
fn test_load_tls_acceptor_succeeds_for_valid_files() {
// `load_tls_acceptor` builds a rustls `ServerConfig`, which requires a process-level crypto
// provider. nextest runs each test in a fresh process, so install the provider here.
ensure_crypto_provider();
load_tls_acceptor(&test_cert_path(), &test_key_path()).unwrap();
}
Loading