From b5afa49b59a411d3f5b9cbea689fb5d473877ac1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:17:16 +0000 Subject: [PATCH 01/10] Initial plan From d0de34f660f33de9f3f0dd6a3a5da929a37ff7a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:21:00 +0000 Subject: [PATCH 02/10] feat: add runtime configuration for one-shot token list via AWF_ONE_SHOT_TOKENS Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/one-shot-token/README.md | 76 ++++++++++++++- .../agent/one-shot-token/one-shot-token.c | 94 ++++++++++++++++++- 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/containers/agent/one-shot-token/README.md b/containers/agent/one-shot-token/README.md index 372054d7..65ed9382 100644 --- a/containers/agent/one-shot-token/README.md +++ b/containers/agent/one-shot-token/README.md @@ -2,13 +2,15 @@ ## Overview -The one-shot token library is an `LD_PRELOAD` shared library that provides **single-use access** to sensitive GitHub token environment variables. When a process reads a protected token via `getenv()`, the library returns the value once and immediately unsets the environment variable, preventing subsequent reads. +The one-shot token library is an `LD_PRELOAD` shared library that provides **single-use access** to sensitive token environment variables. When a process reads a protected token via `getenv()`, the library returns the value once and immediately unsets the environment variable, preventing subsequent reads. This protects against malicious code that might attempt to exfiltrate tokens after the legitimate application has already consumed them. -## Protected Environment Variables +## Configuration -The library intercepts access to these token variables: +### Default Protected Tokens + +By default, the library protects these token variables: **GitHub:** - `COPILOT_GITHUB_TOKEN` @@ -29,6 +31,24 @@ The library intercepts access to these token variables: **Codex:** - `CODEX_API_KEY` +### Custom Token List + +You can configure a custom list of tokens to protect using the `AWF_ONE_SHOT_TOKENS` environment variable: + +```bash +# Protect custom tokens instead of defaults +export AWF_ONE_SHOT_TOKENS="MY_API_KEY,MY_SECRET_TOKEN,CUSTOM_AUTH_KEY" + +# Run your command with the library preloaded +LD_PRELOAD=/usr/local/lib/one-shot-token.so ./your-program +``` + +**Important notes:** +- When `AWF_ONE_SHOT_TOKENS` is set, **only** the tokens in this list are protected (defaults are not included) +- Use comma-separated token names (whitespace is automatically trimmed) +- Maximum of 100 tokens can be protected +- The configuration is read once at library initialization (first `getenv()` call) + ## How It Works ### The LD_PRELOAD Mechanism @@ -154,11 +174,13 @@ This produces `one-shot-token.so` in the current directory. ## Testing +### Basic Test (Default Tokens) + ```bash # Build the library ./build.sh -# Test with a simple program +# Test with a default protected token export GITHUB_TOKEN="test-token-12345" LD_PRELOAD=./one-shot-token.so bash -c ' echo "First read: $(printenv GITHUB_TOKEN)" @@ -168,11 +190,57 @@ LD_PRELOAD=./one-shot-token.so bash -c ' Expected output: ``` +[one-shot-token] Initialized with 11 default token(s) [one-shot-token] Token GITHUB_TOKEN accessed and cleared First read: test-token-12345 Second read: ``` +### Custom Token Test + +```bash +# Build the library +./build.sh + +# Test with custom tokens +export AWF_ONE_SHOT_TOKENS="MY_API_KEY,SECRET_TOKEN" +export MY_API_KEY="secret-value-123" +export SECRET_TOKEN="another-secret" + +LD_PRELOAD=./one-shot-token.so bash -c ' + echo "First MY_API_KEY: $(printenv MY_API_KEY)" + echo "Second MY_API_KEY: $(printenv MY_API_KEY)" + echo "First SECRET_TOKEN: $(printenv SECRET_TOKEN)" + echo "Second SECRET_TOKEN: $(printenv SECRET_TOKEN)" +' +``` + +Expected output: +``` +[one-shot-token] Initialized with 2 custom token(s) from AWF_ONE_SHOT_TOKENS +[one-shot-token] Token MY_API_KEY accessed and cleared +First MY_API_KEY: secret-value-123 +Second MY_API_KEY: +[one-shot-token] Token SECRET_TOKEN accessed and cleared +First SECRET_TOKEN: another-secret +Second SECRET_TOKEN: +``` + +### Integration with AWF + +When using the library with AWF (Agentic Workflow Firewall): + +```bash +# Use default tokens +sudo awf --allow-domains github.com -- your-command + +# Use custom tokens +export AWF_ONE_SHOT_TOKENS="MY_TOKEN,CUSTOM_API_KEY" +sudo -E awf --allow-domains github.com -- your-command +``` + +Note: The `AWF_ONE_SHOT_TOKENS` variable must be exported before running `awf` so it's available when the library initializes. + ## Security Considerations ### What This Protects Against diff --git a/containers/agent/one-shot-token/one-shot-token.c b/containers/agent/one-shot-token/one-shot-token.c index 73b224fc..6fc79f0b 100644 --- a/containers/agent/one-shot-token/one-shot-token.c +++ b/containers/agent/one-shot-token/one-shot-token.c @@ -5,6 +5,10 @@ * On first access, returns the real value and immediately unsets the variable. * Subsequent calls return NULL, preventing token reuse by malicious code. * + * Configuration: + * AWF_ONE_SHOT_TOKENS - Comma-separated list of token names to protect + * If not set, uses built-in defaults + * * Compile: gcc -shared -fPIC -o one-shot-token.so one-shot-token.c -ldl * Usage: LD_PRELOAD=/path/to/one-shot-token.so ./your-program */ @@ -16,8 +20,8 @@ #include #include -/* Sensitive token environment variable names */ -static const char *SENSITIVE_TOKENS[] = { +/* Default sensitive token environment variable names */ +static const char *DEFAULT_SENSITIVE_TOKENS[] = { /* GitHub tokens */ "COPILOT_GITHUB_TOKEN", "GITHUB_TOKEN", @@ -36,12 +40,22 @@ static const char *SENSITIVE_TOKENS[] = { NULL }; +/* Maximum number of tokens we can track (for static allocation) */ +#define MAX_TOKENS 100 + +/* Runtime token list (populated from AWF_ONE_SHOT_TOKENS or defaults) */ +static char *sensitive_tokens[MAX_TOKENS]; +static int num_tokens = 0; + /* Track which tokens have been accessed (one flag per token) */ -static int token_accessed[sizeof(SENSITIVE_TOKENS) / sizeof(SENSITIVE_TOKENS[0])] = {0}; +static int token_accessed[MAX_TOKENS] = {0}; /* Mutex for thread safety */ static pthread_mutex_t token_mutex = PTHREAD_MUTEX_INITIALIZER; +/* Initialization flag */ +static int tokens_initialized = 0; + /* Pointer to the real getenv function */ static char *(*real_getenv)(const char *name) = NULL; @@ -57,12 +71,74 @@ static void init_real_getenv(void) { } } +/** + * Initialize the token list from AWF_ONE_SHOT_TOKENS environment variable + * or use defaults if not set. This is called once at first getenv() call. + */ +static void init_token_list(void) { + if (tokens_initialized) { + return; + } + + /* Get the configuration from environment */ + const char *config = real_getenv("AWF_ONE_SHOT_TOKENS"); + + if (config != NULL && config[0] != '\0') { + /* Parse comma-separated token list */ + char *config_copy = strdup(config); + if (config_copy == NULL) { + fprintf(stderr, "[one-shot-token] ERROR: Failed to allocate memory for token list\n"); + abort(); + } + + char *token = strtok(config_copy, ","); + while (token != NULL && num_tokens < MAX_TOKENS) { + /* Trim leading/trailing whitespace */ + while (*token == ' ' || *token == '\t') token++; + char *end = token + strlen(token) - 1; + while (end > token && (*end == ' ' || *end == '\t')) { + *end = '\0'; + end--; + } + + if (*token != '\0') { + sensitive_tokens[num_tokens] = strdup(token); + if (sensitive_tokens[num_tokens] == NULL) { + fprintf(stderr, "[one-shot-token] ERROR: Failed to allocate memory for token name\n"); + abort(); + } + num_tokens++; + } + + token = strtok(NULL, ","); + } + + free(config_copy); + + fprintf(stderr, "[one-shot-token] Initialized with %d custom token(s) from AWF_ONE_SHOT_TOKENS\n", num_tokens); + } else { + /* Use default token list */ + for (int i = 0; DEFAULT_SENSITIVE_TOKENS[i] != NULL && num_tokens < MAX_TOKENS; i++) { + sensitive_tokens[num_tokens] = strdup(DEFAULT_SENSITIVE_TOKENS[i]); + if (sensitive_tokens[num_tokens] == NULL) { + fprintf(stderr, "[one-shot-token] ERROR: Failed to allocate memory for default token name\n"); + abort(); + } + num_tokens++; + } + + fprintf(stderr, "[one-shot-token] Initialized with %d default token(s)\n", num_tokens); + } + + tokens_initialized = 1; +} + /* Check if a variable name is a sensitive token */ static int get_token_index(const char *name) { if (name == NULL) return -1; - for (int i = 0; SENSITIVE_TOKENS[i] != NULL; i++) { - if (strcmp(name, SENSITIVE_TOKENS[i]) == 0) { + for (int i = 0; i < num_tokens; i++) { + if (strcmp(name, sensitive_tokens[i]) == 0) { return i; } } @@ -81,6 +157,14 @@ static int get_token_index(const char *name) { char *getenv(const char *name) { init_real_getenv(); + /* Initialize token list on first call (but skip if we're initializing) */ + static int initializing = 0; + if (!tokens_initialized && !initializing) { + initializing = 1; + init_token_list(); + initializing = 0; + } + int token_idx = get_token_index(name); /* Not a sensitive token - pass through */ From 351d3942b15cadcc97ed433b5cff4446fcc34ba2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:23:12 +0000 Subject: [PATCH 03/10] fix: address code review feedback for one-shot token library - Make initialization thread-safe using existing mutex - Add cleanup of allocated tokens on memory allocation failure - Use isspace() for comprehensive whitespace trimming (handles newlines, tabs, etc.) Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .../agent/one-shot-token/one-shot-token.c | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/containers/agent/one-shot-token/one-shot-token.c b/containers/agent/one-shot-token/one-shot-token.c index 6fc79f0b..cac86003 100644 --- a/containers/agent/one-shot-token/one-shot-token.c +++ b/containers/agent/one-shot-token/one-shot-token.c @@ -19,6 +19,7 @@ #include #include #include +#include /* Default sensitive token environment variable names */ static const char *DEFAULT_SENSITIVE_TOKENS[] = { @@ -74,6 +75,7 @@ static void init_real_getenv(void) { /** * Initialize the token list from AWF_ONE_SHOT_TOKENS environment variable * or use defaults if not set. This is called once at first getenv() call. + * Note: This function must be called with token_mutex held. */ static void init_token_list(void) { if (tokens_initialized) { @@ -93,10 +95,12 @@ static void init_token_list(void) { char *token = strtok(config_copy, ","); while (token != NULL && num_tokens < MAX_TOKENS) { - /* Trim leading/trailing whitespace */ - while (*token == ' ' || *token == '\t') token++; + /* Trim leading whitespace */ + while (*token && isspace((unsigned char)*token)) token++; + + /* Trim trailing whitespace */ char *end = token + strlen(token) - 1; - while (end > token && (*end == ' ' || *end == '\t')) { + while (end > token && isspace((unsigned char)*end)) { *end = '\0'; end--; } @@ -105,6 +109,11 @@ static void init_token_list(void) { sensitive_tokens[num_tokens] = strdup(token); if (sensitive_tokens[num_tokens] == NULL) { fprintf(stderr, "[one-shot-token] ERROR: Failed to allocate memory for token name\n"); + /* Clean up previously allocated tokens */ + for (int i = 0; i < num_tokens; i++) { + free(sensitive_tokens[i]); + } + free(config_copy); abort(); } num_tokens++; @@ -122,6 +131,10 @@ static void init_token_list(void) { sensitive_tokens[num_tokens] = strdup(DEFAULT_SENSITIVE_TOKENS[i]); if (sensitive_tokens[num_tokens] == NULL) { fprintf(stderr, "[one-shot-token] ERROR: Failed to allocate memory for default token name\n"); + /* Clean up previously allocated tokens */ + for (int j = 0; j < num_tokens; j++) { + free(sensitive_tokens[j]); + } abort(); } num_tokens++; @@ -157,13 +170,12 @@ static int get_token_index(const char *name) { char *getenv(const char *name) { init_real_getenv(); - /* Initialize token list on first call (but skip if we're initializing) */ - static int initializing = 0; - if (!tokens_initialized && !initializing) { - initializing = 1; + /* Initialize token list on first call (thread-safe) */ + pthread_mutex_lock(&token_mutex); + if (!tokens_initialized) { init_token_list(); - initializing = 0; } + pthread_mutex_unlock(&token_mutex); int token_idx = get_token_index(name); From 13239bfc4bf0a91bae212dd8256af304938cd521 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:24:21 +0000 Subject: [PATCH 04/10] fix: resolve race condition and buffer underflow in token parsing - Fix potential buffer underflow when trimming empty strings - Fix race condition by keeping mutex held during get_token_index() call - Ensure thread-safe access to token list during lookup Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .../agent/one-shot-token/one-shot-token.c | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/containers/agent/one-shot-token/one-shot-token.c b/containers/agent/one-shot-token/one-shot-token.c index cac86003..d12516a2 100644 --- a/containers/agent/one-shot-token/one-shot-token.c +++ b/containers/agent/one-shot-token/one-shot-token.c @@ -98,11 +98,13 @@ static void init_token_list(void) { /* Trim leading whitespace */ while (*token && isspace((unsigned char)*token)) token++; - /* Trim trailing whitespace */ - char *end = token + strlen(token) - 1; - while (end > token && isspace((unsigned char)*end)) { - *end = '\0'; - end--; + /* Trim trailing whitespace (only if string is non-empty) */ + if (strlen(token) > 0) { + char *end = token + strlen(token) - 1; + while (end > token && isspace((unsigned char)*end)) { + *end = '\0'; + end--; + } } if (*token != '\0') { @@ -175,18 +177,17 @@ char *getenv(const char *name) { if (!tokens_initialized) { init_token_list(); } - pthread_mutex_unlock(&token_mutex); + /* Get token index while holding mutex to avoid race with initialization */ int token_idx = get_token_index(name); - /* Not a sensitive token - pass through */ + /* Not a sensitive token - release mutex and pass through */ if (token_idx < 0) { + pthread_mutex_unlock(&token_mutex); return real_getenv(name); } - /* Sensitive token - handle one-shot access */ - pthread_mutex_lock(&token_mutex); - + /* Sensitive token - handle one-shot access (mutex already held) */ char *result = NULL; if (!token_accessed[token_idx]) { From 132882c05fd84cf9268d7ffece161b4d0c390650 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:25:07 +0000 Subject: [PATCH 05/10] refactor: optimize strlen usage and add MAX_TOKENS documentation - Cache strlen result to avoid redundant computation - Add inline comment explaining MAX_TOKENS limit rationale Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/one-shot-token/one-shot-token.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/containers/agent/one-shot-token/one-shot-token.c b/containers/agent/one-shot-token/one-shot-token.c index d12516a2..eb243b65 100644 --- a/containers/agent/one-shot-token/one-shot-token.c +++ b/containers/agent/one-shot-token/one-shot-token.c @@ -42,6 +42,8 @@ static const char *DEFAULT_SENSITIVE_TOKENS[] = { }; /* Maximum number of tokens we can track (for static allocation) */ +/* This limit balances memory usage with practical needs - 100 tokens should be + * more than sufficient for any reasonable use case while keeping memory overhead low */ #define MAX_TOKENS 100 /* Runtime token list (populated from AWF_ONE_SHOT_TOKENS or defaults) */ @@ -99,8 +101,9 @@ static void init_token_list(void) { while (*token && isspace((unsigned char)*token)) token++; /* Trim trailing whitespace (only if string is non-empty) */ - if (strlen(token) > 0) { - char *end = token + strlen(token) - 1; + size_t token_len = strlen(token); + if (token_len > 0) { + char *end = token + token_len - 1; while (end > token && isspace((unsigned char)*end)) { *end = '\0'; end--; From 7197e61bf4f11290c522c39043ba832ba41cc565 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:25:54 +0000 Subject: [PATCH 06/10] style: improve comment formatting for MAX_TOKENS Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/one-shot-token/one-shot-token.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/containers/agent/one-shot-token/one-shot-token.c b/containers/agent/one-shot-token/one-shot-token.c index eb243b65..7f75df1b 100644 --- a/containers/agent/one-shot-token/one-shot-token.c +++ b/containers/agent/one-shot-token/one-shot-token.c @@ -41,9 +41,9 @@ static const char *DEFAULT_SENSITIVE_TOKENS[] = { NULL }; -/* Maximum number of tokens we can track (for static allocation) */ -/* This limit balances memory usage with practical needs - 100 tokens should be - * more than sufficient for any reasonable use case while keeping memory overhead low */ +/* Maximum number of tokens we can track (for static allocation). This limit + * balances memory usage with practical needs - 100 tokens should be more than + * sufficient for any reasonable use case while keeping memory overhead low. */ #define MAX_TOKENS 100 /* Runtime token list (populated from AWF_ONE_SHOT_TOKENS or defaults) */ From 22457915b5d96f032549b5499c96c0406c718060 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:28:24 +0000 Subject: [PATCH 07/10] fix: use strtok_r and fallback to defaults for empty token list - Replace strtok() with strtok_r() to avoid interfering with application code - Fall back to default token list if AWF_ONE_SHOT_TOKENS parses to zero tokens - Add warning messages when falling back due to misconfiguration Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .../agent/one-shot-token/one-shot-token.c | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/containers/agent/one-shot-token/one-shot-token.c b/containers/agent/one-shot-token/one-shot-token.c index 7f75df1b..865aa2de 100644 --- a/containers/agent/one-shot-token/one-shot-token.c +++ b/containers/agent/one-shot-token/one-shot-token.c @@ -88,14 +88,15 @@ static void init_token_list(void) { const char *config = real_getenv("AWF_ONE_SHOT_TOKENS"); if (config != NULL && config[0] != '\0') { - /* Parse comma-separated token list */ + /* Parse comma-separated token list using strtok_r for thread safety */ char *config_copy = strdup(config); if (config_copy == NULL) { fprintf(stderr, "[one-shot-token] ERROR: Failed to allocate memory for token list\n"); abort(); } - char *token = strtok(config_copy, ","); + char *saveptr = NULL; + char *token = strtok_r(config_copy, ",", &saveptr); while (token != NULL && num_tokens < MAX_TOKENS) { /* Trim leading whitespace */ while (*token && isspace((unsigned char)*token)) token++; @@ -124,30 +125,40 @@ static void init_token_list(void) { num_tokens++; } - token = strtok(NULL, ","); + token = strtok_r(NULL, ",", &saveptr); } free(config_copy); - fprintf(stderr, "[one-shot-token] Initialized with %d custom token(s) from AWF_ONE_SHOT_TOKENS\n", num_tokens); - } else { - /* Use default token list */ - for (int i = 0; DEFAULT_SENSITIVE_TOKENS[i] != NULL && num_tokens < MAX_TOKENS; i++) { - sensitive_tokens[num_tokens] = strdup(DEFAULT_SENSITIVE_TOKENS[i]); - if (sensitive_tokens[num_tokens] == NULL) { - fprintf(stderr, "[one-shot-token] ERROR: Failed to allocate memory for default token name\n"); - /* Clean up previously allocated tokens */ - for (int j = 0; j < num_tokens; j++) { - free(sensitive_tokens[j]); - } - abort(); + /* If AWF_ONE_SHOT_TOKENS was set but resulted in zero tokens (e.g., ",,," or whitespace only), + * fall back to defaults to avoid silently disabling all protection */ + if (num_tokens == 0) { + fprintf(stderr, "[one-shot-token] WARNING: AWF_ONE_SHOT_TOKENS was set but parsed to zero tokens\n"); + fprintf(stderr, "[one-shot-token] WARNING: Falling back to default token list to maintain protection\n"); + /* Fall through to default initialization below */ + } else { + fprintf(stderr, "[one-shot-token] Initialized with %d custom token(s) from AWF_ONE_SHOT_TOKENS\n", num_tokens); + tokens_initialized = 1; + return; + } + } + + /* Use default token list (when AWF_ONE_SHOT_TOKENS is unset, empty, or parsed to zero tokens) */ + for (int i = 0; DEFAULT_SENSITIVE_TOKENS[i] != NULL && num_tokens < MAX_TOKENS; i++) { + sensitive_tokens[num_tokens] = strdup(DEFAULT_SENSITIVE_TOKENS[i]); + if (sensitive_tokens[num_tokens] == NULL) { + fprintf(stderr, "[one-shot-token] ERROR: Failed to allocate memory for default token name\n"); + /* Clean up previously allocated tokens */ + for (int j = 0; j < num_tokens; j++) { + free(sensitive_tokens[j]); } - num_tokens++; + abort(); } - - fprintf(stderr, "[one-shot-token] Initialized with %d default token(s)\n", num_tokens); + num_tokens++; } + fprintf(stderr, "[one-shot-token] Initialized with %d default token(s)\n", num_tokens); + tokens_initialized = 1; } From 439b53c1eb4963df3cd27395cb9033c7763fe44e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:28:44 +0000 Subject: [PATCH 08/10] docs: update README with fallback behavior and strtok_r details Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/one-shot-token/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/containers/agent/one-shot-token/README.md b/containers/agent/one-shot-token/README.md index 65ed9382..d5af1a2b 100644 --- a/containers/agent/one-shot-token/README.md +++ b/containers/agent/one-shot-token/README.md @@ -44,10 +44,12 @@ LD_PRELOAD=/usr/local/lib/one-shot-token.so ./your-program ``` **Important notes:** -- When `AWF_ONE_SHOT_TOKENS` is set, **only** the tokens in this list are protected (defaults are not included) +- When `AWF_ONE_SHOT_TOKENS` is set with valid tokens, **only** those tokens are protected (defaults are not included) +- If `AWF_ONE_SHOT_TOKENS` is set but contains only whitespace or commas (e.g., `" "` or `",,,"`), the library falls back to the default token list to maintain protection - Use comma-separated token names (whitespace is automatically trimmed) - Maximum of 100 tokens can be protected - The configuration is read once at library initialization (first `getenv()` call) +- Uses `strtok_r()` internally, which is thread-safe and won't interfere with application code using `strtok()` ## How It Works From d50b46746273b0434319aa2ef87832aa64cd2d4b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:29:59 +0000 Subject: [PATCH 09/10] refactor: explicitly reset num_tokens in fallback path for clarity Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/one-shot-token/one-shot-token.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/containers/agent/one-shot-token/one-shot-token.c b/containers/agent/one-shot-token/one-shot-token.c index 865aa2de..2d1a372b 100644 --- a/containers/agent/one-shot-token/one-shot-token.c +++ b/containers/agent/one-shot-token/one-shot-token.c @@ -135,7 +135,8 @@ static void init_token_list(void) { if (num_tokens == 0) { fprintf(stderr, "[one-shot-token] WARNING: AWF_ONE_SHOT_TOKENS was set but parsed to zero tokens\n"); fprintf(stderr, "[one-shot-token] WARNING: Falling back to default token list to maintain protection\n"); - /* Fall through to default initialization below */ + /* Explicitly reset num_tokens before falling through to default initialization */ + num_tokens = 0; } else { fprintf(stderr, "[one-shot-token] Initialized with %d custom token(s) from AWF_ONE_SHOT_TOKENS\n", num_tokens); tokens_initialized = 1; @@ -144,6 +145,7 @@ static void init_token_list(void) { } /* Use default token list (when AWF_ONE_SHOT_TOKENS is unset, empty, or parsed to zero tokens) */ + /* Note: num_tokens should be 0 when we reach here */ for (int i = 0; DEFAULT_SENSITIVE_TOKENS[i] != NULL && num_tokens < MAX_TOKENS; i++) { sensitive_tokens[num_tokens] = strdup(DEFAULT_SENSITIVE_TOKENS[i]); if (sensitive_tokens[num_tokens] == NULL) { From 0a8a050dadf45d4e016000e8d2bf23385af674a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:30:40 +0000 Subject: [PATCH 10/10] docs: clarify defensive programming intent in num_tokens reset Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/one-shot-token/one-shot-token.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/containers/agent/one-shot-token/one-shot-token.c b/containers/agent/one-shot-token/one-shot-token.c index 2d1a372b..e49e025f 100644 --- a/containers/agent/one-shot-token/one-shot-token.c +++ b/containers/agent/one-shot-token/one-shot-token.c @@ -135,7 +135,7 @@ static void init_token_list(void) { if (num_tokens == 0) { fprintf(stderr, "[one-shot-token] WARNING: AWF_ONE_SHOT_TOKENS was set but parsed to zero tokens\n"); fprintf(stderr, "[one-shot-token] WARNING: Falling back to default token list to maintain protection\n"); - /* Explicitly reset num_tokens before falling through to default initialization */ + /* num_tokens is already 0 here; assignment is defensive programming for future refactoring */ num_tokens = 0; } else { fprintf(stderr, "[one-shot-token] Initialized with %d custom token(s) from AWF_ONE_SHOT_TOKENS\n", num_tokens);