Skip to content

Commit 4e22dab

Browse files
committed
Add fuzz testing for query parser and token validator
Implements cargo-fuzz based fuzzing for security-critical components: - Query parser (URL and path-based request parsing) - Token validator (SSRF token validation and file:// handling) Changes: - Add fuzz/ directory with two fuzz targets - Add GitHub Actions workflow to run fuzz tests on PRs (2 min per target) - Expose internal types via lib.rs for fuzzing - Add fuzz/README.md with usage instructions - Update main README with fuzz testing section The fuzz tests run automatically on every PR to catch parsing vulnerabilities, edge cases, and potential crashes before they reach production.
1 parent 8554c56 commit 4e22dab

File tree

18 files changed

+3516
-5
lines changed

18 files changed

+3516
-5
lines changed

.github/workflows/fuzz.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Fuzz Tests
2+
3+
on:
4+
pull_request:
5+
branches: ["main"]
6+
workflow_dispatch:
7+
8+
env:
9+
CARGO_TERM_COLOR: always
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
fuzz:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v5
19+
20+
- name: Install Rust nightly
21+
uses: actions-rs/toolchain@v1
22+
with:
23+
toolchain: nightly
24+
override: true
25+
26+
- name: Install cargo-fuzz
27+
run: cargo install cargo-fuzz
28+
29+
- name: Run fuzz_query_parser (2 min)
30+
run: cargo +nightly fuzz run fuzz_query_parser -- -max_total_time=120
31+
continue-on-error: false
32+
33+
- name: Run fuzz_token_validator (2 min)
34+
run: cargo +nightly fuzz run fuzz_token_validator -- -max_total_time=120
35+
continue-on-error: false
36+
37+
- name: Upload crash artifacts
38+
if: failure()
39+
uses: actions/upload-artifact@v4
40+
with:
41+
name: fuzz-crashes-${{ github.sha }}
42+
path: fuzz/artifacts/
43+
retention-days: 30

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
resolver = "2"
33

44
members = ["aws_secretsmanager_agent", "aws_secretsmanager_caching", "integration-tests"]
5+
exclude = ["fuzz"]

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,12 @@ For an agent architecture, the domain of trust is where the agent endpoint and S
489489

490490
Security conscious applications that are not already using an agent solution with the Secrets Manager credentials locked down to the application should consider using the language\-specific AWS SDKs or caching solutions\. For more information, see [Get secrets](https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets.html)\.
491491

492+
## Fuzz Testing<a name="fuzz-testing"></a>
493+
494+
The AWS Secrets Manager Agent includes fuzz tests to discover security vulnerabilities and edge cases by feeding malformed inputs to critical components. Fuzz tests run automatically on every pull request.
495+
496+
For detailed instructions on running fuzz tests locally, reproducing crashes, and managing the corpus, see [fuzz/README.md](fuzz/README.md).
497+
492498
## Running Integration Tests Locally<a name="integration-tests-local"></a>
493499

494500
The AWS Secrets Manager Agent includes a comprehensive integration test suite that validates functionality against real AWS Secrets Manager. These tests cover caching behavior, security features, configuration options, version management, and error handling scenarios.

aws_secretsmanager_agent/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#[derive(Debug)]
2-
pub(crate) struct HttpError(pub u16, pub String);
2+
pub struct HttpError(pub u16, pub String);
33

44
impl From<url::ParseError> for HttpError {
55
fn from(e: url::ParseError) -> Self {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Library interface for the AWS Secrets Manager Agent
2+
// This exposes internal modules for testing and fuzzing purposes
3+
4+
pub mod cache_manager;
5+
pub mod config;
6+
pub mod constants;
7+
pub mod error;
8+
pub mod logging;
9+
pub mod parse;
10+
pub mod server;
11+
pub mod utils;
12+
13+
// Re-export key types and functions for fuzzing
14+
pub use parse::GSVQuery;
15+
pub use utils::get_token;

aws_secretsmanager_agent/src/parse.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use url::Url;
55
use crate::error::HttpError;
66

77
#[derive(Debug)]
8-
pub(crate) struct GSVQuery {
8+
pub struct GSVQuery {
99
pub secret_id: String,
1010
pub version_id: Option<String>,
1111
pub version_stage: Option<String>,
@@ -23,7 +23,7 @@ impl GSVQuery {
2323
}
2424
}
2525

26-
pub(crate) fn try_from_query(s: &str) -> Result<Self, HttpError> {
26+
pub fn try_from_query(s: &str) -> Result<Self, HttpError> {
2727
// url library can only parse complete URIs. The host/port/scheme used is irrelevant since it is not used
2828
let complete_uri = format!("http://localhost{}", s);
2929

@@ -53,7 +53,7 @@ impl GSVQuery {
5353
Ok(query)
5454
}
5555

56-
pub(crate) fn try_from_path_query(s: &str, path_prefix: &str) -> Result<Self, HttpError> {
56+
pub fn try_from_path_query(s: &str, path_prefix: &str) -> Result<Self, HttpError> {
5757
// url library can only parse complete URIs. The host/port/scheme used is irrelevant since it gets stripped
5858
let complete_uri = format!("http://localhost{}", s);
5959

aws_secretsmanager_agent/src/utils.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ pub fn err_response(err_code: &str, msg: &str) -> String {
5959
///
6060
/// * `Ok(String)` - The SSRF token value.
6161
/// * `Err(Error)` - Error indicating that the variable is not set or could not be read.
62-
#[doc(hidden)]
6362
pub fn get_token(config: &Config) -> Result<String, Box<dyn std::error::Error>> {
6463
// Iterate through the env name list looking for the first variable set
6564
#[allow(clippy::redundant_closure)]

fuzz/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
target
2+
artifacts
3+
coverage
4+
5+
# Ignore generated corpus files (40-char SHA-1 hashes, keep seed corpus)
6+
corpus/*/[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]

0 commit comments

Comments
 (0)