**Title**: Global `ClassDataSource<T>` instances are not disposed until the entire test run ends, causing resource leaks

**Description**
When a test class uses the `[ClassDataSource<T>]` attribute and the generated code calls `TestDataContainer.GetGlobalInstance` to create a single shared instance across all tests, that instance is not disposed until the entire test suite finishes.
This means any `IDisposable` / `IAsyncDisposable` resources held by the data source (database connections, temporary files, sockets, etc.) remain alive long after the last test that depends on them has completed. In large suites or when running tests with `DependsOn` chains, this can cause resource exhaustion and cross-test pollution.

**Expected behavior**
A globally shared class data source instance should be disposed as soon as the last test that *injected* it finishes (regardless of pass, fail, or skip). The framework must keep a reference count of how many tests are using the global instance and dispose it when that count reaches zero.

**Acceptance criteria**
1. After each test completes, the framework decrements a usage count for every global class data source type that test used.
2. When the count for a given type reaches zero, the instance is disposed (asynchronously if it implements `IAsyncDisposable`, otherwise synchronously).
3. The behavior must work correctly with parallel test execution, retries (`[Retry]`), and `[DependsOn]` chains.
4. The existing per-collection shared data (keyed via `SharedKey`) must continue to work without regression; the new mechanism is orthogonal.
5. The `TestInformation` object passed around at runtime must carry enough information to identify which global data source types need to be decremented after the test.

**Guidance / relevant areas**
- In the executor, `DecrementKeyedSharedData` is called after each test. A similar path is needed for global data sources.
- The source generator already produces calls to `TestDataContainer.GetGlobalInstance`. It should also propagate the fact that a given argument is a global class data source so that the generated `TestInformation` includes a list of global types the test depends on.
- `TestDataContainer.GetGlobalInstance` currently does not maintain a reference count; it will need one, along with a decrement method that triggers disposal at zero.
- The end-of-suite cleanup in `TestsExecutor` that currently disposes all global instances should be reconsidered once per-test decrement is implemented; otherwise disposal will happen both per-test and at suite shutdown.


**Title:** Test filtering ignores parenthesised and operator-containing TreeNodeFilter expressions

**Steps to reproduce:**
1. Create a TUnit test project with two test methods, e.g. `MyTest1` and `MyTest2`.
2. Run `dotnet test --filter "FullyQualifiedName~/*/Namespace/TestClass/(MyTest1)"`
   Expected: one test executed.
   Actual: zero tests executed.
3. Run the same with the plain literal `/*/Namespace/TestClass/MyTest1` — one test is found correctly.
4. Other filtering expressions that use TreeNodeFilter operators also fail:
   - `(MyTest1|MyTest2)` → zero tests (expect both)
   - `(MyTest1&MyTest2)` → zero tests
   - `!MyTest1` → zero tests (expect the negation to exclude that test)
   - `Foo\.Bar` (escaped dot) → zero tests (expect literal dot match)

**Observed behaviour:**
Any filter segment that contains one of the TreeNodeFilter operators `(`, `)`, `|`, `&`, `!`, or `\` causes TUnit to return no matching tests at all. Plain literal segments work as expected.

**Expected behaviour:**
The pre-filter optimisation that accelerates test discovery by matching literal names/namespaces should treat a segment as non-literal whenever it contains an operator character from the MTP TreeNodeFilter grammar. In those cases the literal hint for that segment should be dropped, so the authoritative `TreeNodeFilter` downstream can perform the actual matching.

**Additional context:**
- The issue affects all modes (CLI, IDE, dotnet test), but is especially noticeable when using source‑generated test discovery.
- Property‑bag suffixes like `[Category=Smoke]` are already stripped before segment inspection and are not broken.
- Characters that are **not** TreeNodeFilter operators — such as `.` (namespace separator), `+` (nested class), `<`, `>`, `,`, space (generic type names) — should continue to be treated as literal and benefit from the hint optimisation. Only the MTP operator set should trigger the fallback to full filter evaluation.
- This regression prevents any non‑trivial test selection via `dotnet test --filter`, making the feature unusable for grouping, alternation, negation, or escaped literal characters.

fix: treenode filter pre-filter rejects parenthesised segments (#6026) (#6027)


**Title:** Reduce allocations in initialization and injection infrastructure via TCS-based synchronization and fast paths

**Background**
Our property injection, data source initialization, and test‑execution deduplication paths create noticeable heap pressure under high parallel test loads. Currently:

- `DataSourceInitializer.EnsureInitializedAsync` uses `Lazy<Task>` per entry.
- `PropertyInjectionCache` tracks injection work with a `ThreadSafeDictionary<object, Task>`.
- `TestRunner.ExecuteTestAsync` guards against re‑execution with a `ThreadSafeDictionary<string, Task>`.
- `PropertyInjectionService` allocates a new `ConcurrentDictionary` per call for cycle detection.

These patterns cause extra async state machine allocations and needless work when an object is already fully initialized or a test is already running.

**Desired outcome**
Refactor the synchronization primitives in these paths to use `TaskCompletionSource‑based` coordination, with explicit fast‑path checks that avoid entering the slow path when an operation has already completed. The core invariant should stay the same: only one initializer per entity, all waiters obtain the result (success or failure) without re‑executing the work.

Specific behaviors to preserve or improve:

1. **Single initialization guarantee** – For property injection and data‑source initialization, exactly one initialization happens per instance; all concurrent callers wait for that same operation.
2. **Fault‑propagation** – If the single initialization fails, all callers must see (or re‑throw) the original exception.
3. **Allocation‑free completed path** – When an instance is already injected/initialized, the caller should get a near‑zero‑allocation return (no new `Task` or `Lazy` allocation).
4. **Thread‑safety** – All dictionaries remain safe under concurrent access.

**Additional improvements**

- Introduce a lightweight object pool for the visited‑objects dictionaries used in `PropertyInjectionService` so we avoid per‑call allocations (clear and return after use; discard excessively large ones to prevent memory bloat).
- In `EventReceiverOrchestrator.RegisterReceivers`, optimize the loop to skip unnecessary work when duplicate objects or event‑receiver types are encountered by explicitly checking existence before attempting to add, rather than relying solely on the return value of `Add`.

No behavioural change to test execution order, event delivery, or injection logic is intended. All existing test suite behaviour must remain identical.

refactor: optimize injection and initialization tasks for better performance and reduced allocations (#3696)


### Source generator fails to generate tests for `[Test]` and `[CombinativeTest]` methods

**Observed behavior**
Tests that use the `[Test]` attribute or the `[CombinativeTest]` attribute (without extra data-source attributes like `[Arguments]`, `[ClassData]`, etc.) never appear in the generated test registry. They are not discovered by the test runner because the source generator only emits test entries for methods that carry one of the known data attributes.

**Expected behavior**
- A method decorated **only** with `[Test]` should produce a single test case (with no method arguments).
- A method decorated with `[CombinativeTest]` and with parameters annotated with `[CombinativeValues(...)]` should produce a test case for **every combination** of the values provided by those attributes. Each generated test must supply the correct type information for each argument (the generated code must reflect the parameter types, not just value strings).

**Additional context**
- The current `WriteableTestsRetriever` collects test-data attributes (arguments, combinatorial values, etc.) but does not account for the base attributes themselves.
- The existing `BasicTestInvocationGenerator` class appears to be dead code left over from a previous generation strategy; it should be removed so it doesn’t mislead future work.
- The fix should reuse the existing generation pipeline without introducing a separate, parallel code path for these basic test types.


When using `IsEquivalentTo` to compare objects that form circular references (e.g., a parent object that holds a child, and the child holds a reference back to the parent), the assertion either hangs indefinitely or throws a `StackOverflowException`. The comparison logic should detect that it has already visited an object and treat the repeated reference as equivalent rather than recursing into it again, allowing the comparison to complete successfully for structures with back-references.

Detect circular references when using IsEquivalentTo (#1864)


**Title**: Source generator does not combine multiple class‑level data source attributes with method‑level data sources correctly

**Description**
When a test class is decorated with multiple class‑level data source attributes (e.g. `[ClassDataSource<T>]`) and the test method also has data source attributes (e.g. `[MethodDataSource(...)]`), the source generator only picks up one of the class data sources. This results in missing test variants and an incorrect number of generated tests. The expected behaviour is that the source generator should produce the cartesian product: for every combination of a class‑level data source value and a method‑level data source value, a distinct test should be generated.

**Steps to reproduce (conceptual)**
1. Create a test class with two (or more) `ClassDataSource<T>` attributes, each providing different argument values.
2. Add a test method that uses one or more method‑level data source attributes.
3. Build the project and examine the generated test source files.
4. Observe that only one class‑level data source is considered; the number of generated tests equals the number of method data source values multiplied by 1, instead of multiplied by the total number of distinct class data source values.

**Additional constraint**
`DataSourceDrivenTestAttribute` is currently usable on classes. This attribute was not intended for class‑level usage and its presence on a class with other data source attributes leads to confusing behaviour. To avoid further misuse, the attribute should be restricted to methods only (remove `AttributeTargets.Class` from its `[AttributeUsage]`). Existing test classes that had `[DataSourceDrivenTest]` on the class should be updated to use `ClassDataSource` instead.

**Concrete example for verification**
The repository already contains a test class `TUnit.TestProject.DataSourceClassCombinedWithDataSourceMethod` that demonstrates the problem. That class has three method‑level `[MethodDataSource]` attributes and three class‑level data source attributes (via `CommonTestData`). After the fix, the source generator is expected to emit 9 distinct test files (3 method combinations × 3 class combinations). A developer working on this task can use that class as a manual check.

**Guidance**
- The `ClassArgumentsRetriever.GetClassArguments` method currently returns a single `ArgumentsContainer`. It should return all the class‑level data source containers so the caller can combine them with method‑level containers.
- The caller in `TestSourceDataModelRetriever.ParseTestDatas` already loops over method argument containers; it now needs an inner loop over class argument containers to produce the full set of test data models.
- Ensure the fix handles classes with no data source attributes (they should still work as a single “empty” argument container) and classes with exactly one such attribute (no change in behaviour).
- After restricting `DataSourceDrivenTestAttribute` to methods, any class‑level usage in existing test classes must be removed; `TUnit.TestProject.DataSourceClassCombinedWithDataSourceMethod` already has that attribute removed in the current branch, so no additional migration is needed.


**Generated Filename Collisions for Nested Test Classes with Identical Short Names**

When test classes are nested inside other test classes, the source‑generator currently uses only the innermost class name to build the generated file name.
If two top‑level test classes (or two separate namespace‑scoped test classes) each contain a nested test class with the same simple name, the generated filenames collide. This causes a build failure (duplicate generated files) or silently hides tests because one generation overwrites the other.

**Acceptance criteria**
- Filenames produced for nested test classes must be unique even when different outer classes contain inner classes with identical names.
- The filename must reflect all containing type names, from outermost to innermost, separated by underscores.
- The existing namespace prefix, sanitization rules, and handling of generic type arguments must be preserved.
- No test definitions should be lost or overwritten due to naming collisions.

**Example scenario**
Two test files in the same namespace:
- `Class1Tests` has nested `Constructor` and `AddAddend` test classes.
- `Class2Tests` also has nested `Constructor` and `AddAddend` test classes.

Without the fix, both `Constructor` nested classes would try to produce the same generated filename. After the fix, the filenames should distinguish them by incorporating the full chain of containing types (e.g., `..._Class1Tests_Constructor` and `..._Class2Tests_Constructor`).

fix: enhance filename generation to include all containing types (#3686)


```
Title: InvalidOperationException "Collection was modified" when registering test/hook sources during discovery via hooks

Description:
I’m using a custom test discovery hook (`ITestDiscoveryHookSource`) to dynamically load additional assemblies and register their test sources and hooks. Inside the `BeforeTestDiscovery` callback I call `SourceRegistrar.Register(...)`, `SourceRegistrar.RegisterTestHookSource(...)`, and similar methods. However, when TUnit begins to process those sources (in `TestsCollector.GetTests()` and `HooksCollector` methods), I get an `InvalidOperationException`:

  "Collection was modified; enumeration operation may not execute."

The stack trace points to `List<T>.Enumerator.MoveNext()` being called while iterating one of the internal source collections, such as `Sources.TestSources`, `Sources.TestHookSources`, `Sources.ClassHookSources`, `Sources.AssemblyHookSources`, `Sources.TestSessionHookSources`, or `Sources.TestDiscoveryHookSources`.

The problem occurs because these collections can be modified (via `Register*` methods on `SourceRegistrar`) by assembly load hooks while they are being enumerated during discovery/collection. This makes it impossible to extend TUnit's test surface dynamically through supported hooks without crashing.

Expected behavior:
1. Adding sources via `SourceRegistrar` methods at any point (including during discovery) should never cause a collection-modified exception.
2. All sources registered – even those added after the collectors have started draining the collections – should be discovered and executed. No source should be silently lost.
3. The mechanism should be safe for concurrent additions from assembly load callbacks, background tasks, or hook invocation.

Actual behavior:
- When any `Register*` call on `SourceRegistrar` runs after test source or hook enumeration has started, an `InvalidOperationException` is thrown.
- If the exception is caught and ignored, sources registered late may still be missed because the `foreach` enumeration has already passed that point.

Impact: Extensibility scenarios (e.g., plugin systems, on-demand assembly scanning, multi-module test repositories) are broken, as they rely on the ability to populate source registrations during discovery hooks without races.
```

Use Concurrent Collections to fix assemblies loading in and modifying collections during hook or test object population (#2265)


**Reduce allocations in data-driven test expansion**

The Cartesian product computed by `[MatrixDataSource]` and `[CombinedDataSources]` uses LINQ `Aggregate`/`SelectMany` chains that allocate many intermediate enumerables and cause a final `ToArray()` per row. Similarly, exclusion handling for `[MatrixExclusion]` uses LINQ (`Where`/`Cast`/`Select`/`ToArray`) and a per-row `Any()` with a closure. The `MethodDataSource` async-enumerable detection calls `Type.GetInterfaces()` on every invocation instead of caching the result, and `DataGeneratorMetadataCreator` does an O(N*M) `FirstOrDefault` scan when matching property data sources.

Rewire these hot paths with explicit loops to eliminate the extra allocations. Keep row content and ordering identical to the current release: last dimension must vary fastest in the Cartesian product, and the exclusion logic must produce the exact same set of rows. All existing data-driven test suites must continue to pass unchanged.

perf(core): de-LINQ data-source expansion (#6110)


**Summary**: Test class instances that implement `IDisposable` or `IAsyncDisposable` are never disposed, causing resource leaks and potential flakiness.

**Steps to reproduce**:
Write a test class that implements `IDisposable` (or `IAsyncDisposable`) and holds an external resource (file handle, socket, etc.). Run it via TUnit. The resource is never released, even after test execution finishes.

**Expected behavior**:
After a test completes (or fails), the test class instance should be disposed if it implements either disposal interface. Disposal must happen in a `finally` block so it runs even when the test throws, and must not accidentally suppress a retry or repeat loop.

**Impact**:
- **Resource exhaustion** in long test runs (file handles, connections, unmanaged memory).
- **Hidden cross-test contamination** when undispoded state leaks into other tests or subsequent runs.
- **Ad-hoc workarounds** in user test code (manual disposal in teardown), which defeats the framework’s lifecycle guarantees.

**Context**:
TUnit creates test class instances via `TestClassCreator` and runs them with `SingleTestExecutor`. Currently there is no disposal step anywhere in the test execution pipeline.

Please add proper disposal of test class instances after execution, handling both synchronous and asynchronous disposal patterns, and ensuring the instance is always cleaned up regardless of test outcome.


**Request:** Add a Roslyn analyzer that validates `[ClassDataSource]` usage on test classes and test methods.

**Problem:**
There is currently no compile-time check that a `[ClassDataSource]` attribute has a corresponding parameter. A developer can add `[ClassDataSource<int>]` to a class or method without providing an `int` parameter in the constructor or method signature. This leads to confusing runtime failures instead of catching the mistake at build time.

**Expected behavior:**
- When `[ClassDataSource]` is applied to a **class**, the class constructor must contain at least one parameter whose type is assignable from the data source type (after implicit conversions).
- When `[ClassDataSource]` is applied to a **test method**, the method must contain at least one such parameter.
- Both generic (e.g. `[ClassDataSource<int>]`) and non-generic (`[ClassDataSource(typeof(int))]`) forms should be supported.
- If no matching parameter exists, report a compilation error (not a warning).
- The check should apply regardless of other attributes on the method (e.g. `[DataSourceDrivenTest]`) or class.

**Impact:**
This missing validation results in silent runtime errors, making the framework feel brittle and slowing development. Adding an analyzer will give immediate feedback, akin to how other test data source attributes (like `[DataSourceDrivenTest]`) already have parameter-count/type checks.


**Bug: `MethodDataSource` returning `TestDataRow<Func<T>>` does not invoke the inner Func or spread its result**

When a `[MethodDataSource]` method returns `IEnumerable<Func<T>>` and `T` is a tuple (e.g., `(int, int, int)`), the framework correctly invokes the Func and spreads the tuple elements across the test method’s parameters. The values also appear in parameterized `DisplayName` templates (`$arg1`, `$arg2`, `$paramName`, etc.).

The same behaviour is expected when the source is wrapped in `TestDataRow` — i.e., `IEnumerable<TestDataRow<Func<T>>>`. Instead, two problems are observed:

1. The inner `Func<T>` is **not invoked**. The test method receives a single argument of type `Func<T>` (the delegate itself) rather than the values the Func returns. When `T` is a tuple, this means the tuple is never spread, so the test method’s parameters (e.g., `int a, int b, int c`) are not bound correctly.
2. Parameterized `DisplayName` placeholders (`$arg1`, `$paramName`, etc.) are **not substituted** with the resolved values. Since the Func is never called, the values are unavailable, and the display name remains a literal template. Rows with identical templates therefore produce duplicate test-case names, causing some tests to be silently dropped.

A secondary, related issue also affects display-name substitution: when a test method has a parameter with a single-character name (e.g., `int a`) and a `DisplayName` uses `$arg1`, the current substitution logic may incorrectly match the parameter name `a` inside the placeholder `$arg1`, producing garbled output like `1rg1`. This overlap should be avoided; positional placeholders (`$argN`) should be unambiguous and never be corrupted by shorter parameter names.

**Desired behaviour**
- A `MethodDataSource` that returns `IEnumerable<TestDataRow<Func<T>>>` must invoke the inner `Func<T>` and, if the result is a tuple, spread its elements across the test method’s parameters — exactly as it does for bare `IEnumerable<Func<T>>`.
- Parameterized display names must be populated with the resolved argument values. Placeholder substitution must be word-boundary-aware so that a placeholder like `$a` does not match inside `$arg1`, and ideally positional placeholders should be resolved before named placeholders to prevent collisions.

**Impact**
- Using `TestDataRow` with `Func<Tuple>` is currently unusable for parameterised tests that rely on tuple spreading or custom display names, forcing users to avoid the wrapper or work around it.
- The display-name collision bug produces confusing output and can cause test cases to be dropped when multiple rows share the same unresolved template.

+semver:minor - fix: invoke inner Func for TestDataRow<Func<T>> data sources (#6161) (#6183)


**Request:**
`BeforeHookTaskCache.GetOrCreateBeforeClassTask` and `AfterHookPairTracker.GetOrCreateAfterClassTask` serialize all test classes behind a single shared `Lock`. For large suites (e.g. 1000+ test classes), this causes measurable contention on startup/teardown hooks. Replace the shared class-level lock with the per‑key `ThreadSafeDictionary<,>.GetOrAdd` pattern **already used** for assembly‑level tasks (`GetOrCreateBeforeAssemblyTask`). The common cache‑hit path must remain allocation‑free (lock‑free `TryGetValue` fast path).

In `TestScheduler`, when the user requests “unlimited” parallelism (value `0`), the scheduler currently branches into a code path that uses `Parallel.ForEachAsync` **without a `MaxDegreeOfParallelism` cap**, eventually saturating IOCP threads on large test sets. Unify the two paths: the unlimited case must flow through the same semaphore‑based limiter as the bounded case, with the limit resolved to the default cap (`Environment.ProcessorCount * 4`). The `SemaphoreSlim` should never be null.

**Invariants:**
- Hook tasks must still execute **exactly once** per class; using `Lazy<T>` with `ExecutionAndPublication` inside the dictionary is acceptable and already present in the assembly‑task path.
- The behaviour of the `0` parallelism setting must be identical to the default cap (no regression to truly unbounded parallelism).

perf(engine): reduce lock contention in scheduling and hook caches (#5686) (#5693)


```
Bug: AwaitAssertion analyzer misses missing await on Assert.That inside using (Assert.Multiple())

When an `Assert.That(...)` expression is placed inside a `using (Assert.Multiple())` block and is not awaited, the analyzer's "AwaitAssertion" diagnostic (TUnitAssertions0002) should still fire. Currently it does not—the assertion call is silently accepted as if it were awaited.

Expected behavior:
- Given code like:

    using (Assert.Multiple())
    {
        Assert.That(1).IsEqualTo(1);
    }

  The analyzer should report that `Assert.That(...)` must be awaited.

- If the assertion is correctly awaited (`await Assert.That(1).IsEqualTo(1);`), no diagnostic should appear, even inside `using (Assert.Multiple())`.

- The existing `DisposableUsingMultiple` diagnostic (TUnitAssertions0004) for `Assert.Multiple()` used without a `using` statement must continue to work as before.

- The "awaited" check for `Assert.That` outside of any `using` block must not change behavior.

Constraints:
- Do not just widen the ancestor search—make sure the awaiter check and the using-block check are properly separated so they don't cross-contaminate. `Assert.Multiple()` needs a `using`; `Assert.That` needs an `await`. One inside the other should not satisfy the other's requirement.
```


**Title:** `SingleTestExecutor` duplicates setup/teardown lifecycle; should delegate entirely to generated test wrappers

### Observed behavior
When a test class defines `[SetUp]`, `[CleanUp]`, or `[OnlyOnceSetUp]` methods, or implements `IDisposable`/`IAsyncDisposable`, the test lifecycle steps are sometimes duplicated or run in the wrong order. This happens because both the source-generated test wrapper (which already invokes setup, the test method, and teardown) **and** `SingleTestExecutor` (which also discovers and runs these lifecycle methods via reflection and `ClassWalker`) execute their own version of the setup/teardown logic.

This leads to:
- Setup methods being called twice (once from the generated wrapper, once from the executor).
- Cleanup/Dispose being invoked from both paths, potentially masking or losing exceptions.
- Unnecessary reflection-based discovery at runtime when the same information is already available at code generation time.

### Expected behavior
`SingleTestExecutor` should **not** be responsible for discovering and running setup/cleanup methods. Instead, it should rely exclusively on the generated test wrapper to manage the full lifecycle (setup, test execution, teardown, and safe disposal). The executor should obtain the pre-built test delegate from `TestDictionary` and simply invoke it (e.g., via `Invoke()`).

The generated code should continue to use `RunAsync`/`RunSafelyAsync` helpers to safely execute user code and aggregate exceptions. These helpers should be defined in a shared location accessible to generated code (the source generator currently embeds duplicate copies).

Requirements:
- Remove all reflection-based setup/cleanup/dispose logic from `SingleTestExecutor` (including the `ClassWalker` dependency).
- Make the executor call the test delegate from `TestDictionary` and do nothing else for lifecycle management.
- Ensure that all setup, test, and teardown steps execute exactly once, in the correct order, as defined by the generated wrapper.


We need to craft a bug report that describes the `--disable-logo` not working, and the banner appearing even when logging at Information level, without revealing the exact code changes. We'll mention the `DisableLogoCommandProvider` registration and `BannerCapability` needing to respect `ILoggerFactory` verbosity. The request should be concise but complete.```
Title: --disable-logo option has no effect & banner always displayed with verbose/info logging

Description:
I’m trying to clean up the test output in a CI log and when running tests
with `--disable-logo`. Regardless of whether I pass that flag, the full ASCII
art banner still appears every run. From a quick look at the code, it seems
the `DisableLogoCommandProvider` is defined but never registered in the
command-line providers, so the `BannerCapability` never sees the option as set.

Beyond the flag not working, I’d also expect the banner to be automatically
suppressed (and replaced with just the concise runtime details) whenever the
effective logging level is Information or higher. When I’m running with verbose
logging, the large banner is pure noise and it’s not what I’d expect from
any modern test runner.

Expected behaviour:
- `--disable-logo` should prevent the full ASCII banner from being shown;
  only the compact runtime details line should be emitted.
- Even without `--disable-logo`, if Information (or lower-level) logging is
  active, the banner should be replaced with the runtime details to avoid
  cluttering the output.

Impact:
- CI logs are unnecessarily large and hard to read.
- The advertised `--disable-logo` flag appears broken.
- Verbose diagnostic runs still include a large banner that provides no
  actionable information.
```

feat: add DisableLogoCommandProvider and improve BannerCapability initialization (#2736)


Subject: Improve expression tracking and type safety in assertions

Several assertion methods do not participate in expression tracking, producing less helpful error messages than other methods. Additionally, some type-related assertions accept any type parameter, allowing nonsensical assertions that can never succeed to compile.

**Expression tracking missing:**
- `IsNot.Null`
- `IsNot.TypeOf`
- `IsNot.AssignableTo`
- `IsNot.AssignableFrom`
- Both `Has.Property` overloads

These should include the caller expression in their error output, matching the behavior already present in `Is.EqualTo`, `Is.TypeOf`, and similar methods.

**Missing type constraints:**
- `Is.TypeOf<TExpected>()`
- `Is.AssignableTo<TExpected>()`
- `Is.AssignableFrom<TExpected>()`

These currently allow any `TExpected` type, even when it is impossible for the actual value to satisfy the assertion (e.g., asserting that a string is assignable to `int`). Add a generic constraint `where TExpected : TActual` to block such invalid assertions at compile time.

The fix should use the same patterns already established in other assertion methods (e.g., `[CallerArgumentExpression]` and the expression-tracking helper used by `Is.EqualTo`). No new error message strings are needed; the existing infrastructure already formats expressions when they are provided.


**Title:** Fix skipped test result timing/output and unify cancellation/timeout handling

**Problem**
- Skipped tests produce `TestResult` with zero duration and no captured standard/error output, even when pre-hooks or explicit-filter checks ran and may have produced output.
- The test executor carries a duplicate `CancellationTokenSource` that is just a reference to the engine’s source. This leads to inconsistent cancellation checks (field vs. engine token) and makes it unclear which token drives session cancellation.
- `RunTimeoutAsync` accepts a nullable timeout; when no timeout is set, it still sets up a completion that will cancel the test via the linked token upon timeout task completion. This entanglement causes unnecessary overhead and potential interference with cancellation semantics for tests without a timeout.
- The separate `ExecuteTest` method duplicates logic already present in `ExecuteTestInternalAsync`, making the flow harder to maintain and increasing the chance that skipped tests are handled differently from other outcomes.

**Acceptance criteria**
1. **Skipped test results** must report timing derived from the test’s actual start time (the point after dependency-waiting and before execution) and include any standard/error output captured up to the skip (same shape as passed/failed results).
2. **Session cancellation** must rely exclusively on the `EngineCancellationToken` token propagated through the executor; the redundant `CancellationTokenSource` field and its usage should be removed. Any cancelled/skip condition should be evaluated against the engine token directly.
3. **Timeout behaviour** must be applied only when a test explicitly defines a non-zero timeout. Tests without a timeout must bypass `RunTimeoutAsync` entirely and not be subject to a timed cancellation completion.
4. **The execution flow** within `ExecuteTestInternalAsync` should inline the pre-test checks (explicit attribute, skip reason, session cancellation), static before-hooks, and retry execution instead of delegating to a separate `ExecuteTest` method. This ensures a single, consistent path for all test statuses.

**Non-obvious edge cases**
- Skipped tests that were cancelled by the engine token *after* some hooks/output should still record that output.
- The removal of the separate `CancellationTokenSource` must not affect per-test cancellation tokens linked from `TestContext.LinkedCancellationTokens`; those should still be linked with the engine token when present.
- `GetTimingProperty` must no longer accept a nullable start; a test that reaches the execution stage always has a definite start time.
- The change to `RunTimeoutAsync` must not break any existing callers that pass `TimeSpan.Zero` or a default `TimeSpan`—those should be treated as “no timeout”.

Merge ExecuteTest back into ExecuteTestInternalAsync (#1237)


```
Title: Console output mixing between parallel tests

When multiple tests run in parallel, the captured standard output (via
TestContext.GetStandardOutput()) can contain output from other tests, or
output can be missing.

The root cause is that the console interceptor's line buffer — used to hold
partial writes from Console.Write() until a newline arrives — is a shared
singleton across all parallel tests. For example, if Test A calls
Console.Write("A") and then Test B calls Console.WriteLine("B"), Test B's
output may incorrectly include "A".

We need each test's console output to be fully isolated. This applies to
both stdout and stderr interception.

Requirements:
- No cross-contamination: a test's captured output must only contain its own
  writes, regardless of how many tests run concurrently.
- Partial writes still aggregate correctly within a test.
- Thread safety: concurrent writes from the same test should be safe.
- All existing capture tests (CaptureOutputTests) must keep passing.

Please scope the line buffer to the current test context (using the existing
Context.Current mechanism), similar to how OutputWriter and ErrorOutputWriter
are already per-context. The OptimizedConsoleInterceptor base class should
source its buffer from the test's context rather than an instance field.
```

fix: prevent console output mixing between parallel tests (#4545) (#4549)


We need to be able to mark individual test methods with a timeout so that a slow or hung test does not stall the entire test suite. Currently TUnit has no mechanism for per‑test timeouts — if a test enters an infinite loop or blocks on a long‑running async operation without completing, the run hangs forever. This is especially painful in CI pipelines where a single hung test can waste hours of wall‑clock time.

## Requested Behavior

- Introduce an attribute that can be placed on a test method to specify a timeout duration in milliseconds.
- When a test method is decorated with the timeout attribute, the engine should start the test as usual but cancel it if the test does not finish within the given duration.
- A cancelled test should be treated as a failure (it should not pass, and it should be clear that the timeout was the cause).
- Tests that complete within the timeout, or tests that do **not** carry the attribute, must behave exactly as they do today — no performance regressions, no spurious cancellations.
- The timeout mechanism should work for both synchronous and asynchronous test methods. For async tests, the cancellation should cut short any outstanding `await`; the test should not be allowed to continue running silently after the timeout fires.
- The timeout value of `0` should be treated as “no timeout” (keep the default, indefinite behaviour).
  *(If that feels unnatural, a missing attribute is the primary “no timeout” signal — but if someone writes `Timeout(0)` we shouldn’t cancel immediately.)*

## Additional Constraints / Safety

- The implementation must cooperate with any global cancellation token that the test runner already uses (e.g., for aborting the whole test run). A per‑test timeout should not ignore or clobber a higher‑level cancellation request.
- Resources (tasks, timers, linked cancellation sources) created per test must be properly disposed or allowed to be garbage‑collected after the test finishes, regardless of whether it timed out or completed normally.
- There should be no race where a completing test is incorrectly reported as timed out, or where a timeout fires but the test is still considered “running”.

## Example (illustrative)

```csharp
[Test, Timeout(500)]
public async Task SlowTest_CancelledAfterTimeout()
{
    await Task.Delay(TimeSpan.FromSeconds(5));
    // This should never be reached; the test should fail after ~500ms.
}
```

A test like the above should consistently fail with a timeout-related error, and the test runner should move on to the next test immediately rather than waiting for the `Task.Delay` to finish.

---

The key user‑visible invariants are:

1. **Opt‑in**: only tests with the timeout attribute are affected.
2. **Fail‑fast**: a timed‑out test is reported as a failure; the suite continues.
3. **Cooperative cancellation**: uses the standard .NET cancellation model so that test code that respects `CancellationToken` can clean up promptly.
4. **No leaks**: no dangling tasks or memory impact from repeated timed‑out tests.

This feature is important for confidence in automated test runs — especially when test code interacts with external resources that may hang unpredictably.


When scanning directories with the filesystem source:

- Exclusion patterns should prune entire subdirectory trees during enumeration, not just reject files after descending into them.
- The filter should be applied before the regular-file check so that excluded/non-included entries are silently skipped without logging about non‑regular files.
- Include patterns must still work: a directory that is not included but also not explicitly excluded must still be descended into (so that any included descendants are found). Only a directory that itself matches an exclude rule should be skipped with `fs.SkipDir`.

Allow filesystem exclusion to eagerly prune the enumerated tree (#4008)


The SatisMeter credential detector in TruffleHog is currently scanning for email, password, and a project key, then trying to authenticate with Basic Auth. Since SatisMeter changed their API to use Bearer tokens (32-character tokens) alongside project IDs (24-character identifiers), the existing detector no longer works and creates a blind spot for live secrets.

**Acceptance Criteria**

1. **What to scan:** The detector must find credentials in data that contain both a SatisMeter project ID and a SatisMeter API token. The project ID is a 24‑character alphanumeric string, and the token is a 32‑character alphanumeric string. Both must appear within the same chunk of scanned data.
2. **Verification behavior:** When verification is enabled, the detector must send an HTTP GET request to `https://app.satismeter.com/api/users?project=<PROJECT_ID>` with the header `Authorization: Bearer <TOKEN>`. The response must be handled as follows:
   - HTTP 200: mark the result as **verified**.
   - HTTP 401, 403, or 404: mark the result as **unverified** with no error.
   - Any other status code: mark as **unverified** and record a verification error with the unexpected status code.
3. **No false matches from old patterns:** The detector must **not** search for email addresses or passwords related to SatisMeter (e.g., patterns like `satismeter@example.com` or `satismeterSecureP@ss123`). The old email/password/BasicAuth logic must be completely removed to avoid false positive matches against obsolete or non‑credential data.
4. **Output shape:** The resulting `RawV2` field must contain the concatenation of the discovered project ID and token (project ID first, token second), making it easy to copy the combined secret for downstream use.
5. **Resource management:** The HTTP response body must be read and closed correctly during verification to avoid leaking resources; any `io.Copy`/`Close` pattern is fine as long as it’s robust.
6. **Existing detector identity preserved:** The detector type, description, and overall integration with the scanner registry must not change — it should still be identified as `SatismeterProjectkey` and fire when the keywords (`satismeter`) are present.

The end goal is that a scan of repositories or files containing a valid `project ID` and `token` pair will be detected and successfully verified, while previously working (but now dead) credentials are no longer reported.


Title: Figma personal access token detectors do not distinguish definitively invalid tokens from failed verifications, causing lost signals and triage confusion

Body:
Currently, the Figma personal access token detectors (v1 and v2) only set `Verified: false` when the API call to `https://api.figma.com/v1/me` does not return a 2xx status. In many cases the API will return `403 Forbidden` for an invalid or expired token—that is a definitive “this token does not work” condition. However, the present logic treats any non‑2xx response the same as a network timeout or a completely unexpected status (e.g. `404`, `500`, DNS failure). Moreover, when the HTTP client itself returns an error (timeout, connection refused) the detector currently drops the result entirely instead of recording a failure. This makes it impossible to tell a token that the API explicitly rejected (high confidence “unverified”) from a verification attempt that could not be carried out (environmental or transient failure). Operators see all unverified results as low‑priority noise or miss them when they actually point to a broken scanning pipeline.

Expected behaviour:
1. **Tri‑state verification** – a `VerificationError` field should be populated when verification could not be completed reliably, specifically:
   - HTTP status `200‑299` → `Verified = true`, no error.
   - HTTP status `403` → `Verified = false`, no error (the token is definitively not valid/not active).
   - Any other HTTP status or a client‑side error (network failure, timeout) → `Verified = false`, `VerificationError` set to a non‑nil error describing the problem.
2. **False‑positive checks are preserved but reordered** – known false‑positive patterns (those returned by `detectors.IsKnownFalsePositive`) must still be evaluated after verification. If a result is not verified **and** matches a known false‑positive pattern it should be discarded entirely (even if a verification error occurred) to avoid alerting on test/training data. If it does not match a false‑positive pattern, the result is kept—with or without a verification error as determined above.

Impact:
- Operators can distinguish between “this find was confirmed invalid by the API” and “the scanner could not check this find due to an error”, enabling better alert prioritisation and faster debugging of verification‑step failures.
- No results are silently swallowed on client errors unless they are matched by a false‑positive pattern, preventing loss of potential leads.
- The existing public surface of the detector (types, keywords, regex) remains unchanged; only the verification‑result lifecycle is refined.

The change should apply to both the v1 and v2 Figma personal access token detectors, as they share the same API and verification logic.

update figma to use tri-state verification (#1814)


**Title:** Git scanner duplicates work for unchanged files and is slow; should scan each unique blob once

**Observed Behavior**
When scanning a git repository that contains files unchanged across many commits, TruffleHog processes the same file content repeatedly. For example, scanning a repository like `dustin-decker/secretsandstuff` produces multiple chunk messages for the same file blob (same content, same hash) every time it appears in a commit. This makes scanning large histories unnecessarily slow and fills the output with redundant results.

**Expected Behavior**
Scanning should be based on *full file content* rather than per‑commit diffs. After the first time a file blob is seen (identified by its content hash), it should not be scanned again. The output should contain one chunk per unique blob that passes the filter, with metadata reporting the commit and email from the first commit where that blob appears (or the most relevant commit). Scanning of unstaged working-tree changes must still happen exactly as before.

Additional constraints:

- Respect existing options: `--include_paths`/`--exclude_paths` filters (applied to file names), `--branch`, and the `--since_commit` and `--max_depth` flags.
- `--since_commit` should work correctly: if the given commit’s hash matches a commit in the log, only commits after it (based on committer time) are scanned.
- `--max_depth` should limit the number of most recent commits scanned from the tip, counting by unique committer timestamps, not by individual commits (commits with the same timestamp are grouped).
- The scanner must still provide all relevant metadata per chunk (repository URL, commit hash, author email, file path).
- The deduplication must be across the entire scan session, so if a blob appears in multiple branches covered by the scan, it’s only emitted once.

**Impact**
The current patch‑scanning approach can make scanning of repositories with deep histories and many shared files (e.g., large monorepos, forks) impractically slow. By deduplicating on blob hashes, we eliminate redundant work, significantly reducing runtime while preserving correctness. This is essential for CI/CD integrations and large‑scale secret detection at organizations with many repositories.


As a user scanning code for exposed secrets, I want TruffleHog to detect Beebole API keys so that I can prevent accidental leakage of these credentials.

Acceptance criteria:
- A new detector for Beebole (the `beebole.com` service) must be available under the detectors package, using the standard detector interface.
- The detector must match potential Beebole secrets that consist of exactly 40 lowercase alphanumeric characters when found in proximity to the keyword “beebole”.
- When verification is enabled (`--verification` flag), the detector must:
  - send a POST request to `https://beebole-apps.com/api/v2` with a JSON body `{"service": "custom_field.list"}`,
  - authenticate using Basic auth where the token is `base64("{{key}}:X")`,
  - treat any 2xx HTTP status as a verified leak.
- Unverified matches must be checked against the project’s known-false-positive list (e.g., common test strings) and discarded if they appear non-random or placeholder values.
- The detector must integrate seamlessly: it can be discovered automatically, respects the concurrency and context cancellation controls, and works on all supported data sources (git, GitHub, S3, etc.).
- Unit tests must exercise detection of valid (verified) keys, invalid/unverifiable keys, and absence of false positives from non‑secret input, using the standard test helpers and benchmark data.


# Feature Request: Paymoapp API Token Detector

## Overview
Add a new detector for Paymoapp API tokens to TruffleHog so that scans can identify and optionally validate these secrets.

## Acceptance Criteria

1. **Token Discovery**
   When scanning any data source (git repos, files, etc.), the system must identify Paymoapp API tokens that satisfy all of the following:
   - The token is exactly 44 alphanumeric characters (`[a-zA-Z0-9]{44}`).
   - The token is preceded by the literal string `paymoapp`, case‑insensitive, and appears after a word boundary (so that it is not an accidental substring of a longer identifier).
   - The token itself is immediately surrounded by non‑word characters (i.e., boundaries), to avoid false matches inside longer strings.

2. **Verification (optional, when verification is enabled)**
   If verification is requested (`verify = true`), the detector must:
   - Issue a `GET` request to `https://app.paymoapp.com/api/me` with an `Authorization: Basic <token>` header.
   - Accept any HTTP response with a status code in the `2xx` range as proof that the token is valid; set `Verified = true` on the result.
   - If the server responds with a non‑2xx status, leave the result as unverified (`Verified = false`).

3. **False‑positive filtering**
   When verification fails (non‑2xx response), the detector must still apply the standard TruffleHog false‑positive check (`detectors.IsKnownFalsePositive`) to the raw token. If the token matches a known false positive pattern, it must be **suppressed entirely** and not included in the results.

4. **Pre‑filtering**
   The detector must advertise the keyword `"paymoapp"` via the `Keywords()` method so that the engine can skip chunks that do not contain this string, improving scan efficiency.

5. **Conformance to detector interface**
   The implementation must satisfy the `detectors.Detector` interface (using a `Scanner` struct, implementing `FromData`, `Keywords`, and using the standard `CleanResults` sanitization). The token must be associated with the detector type `DetectorType_Paymoapp` (already defined in the pb definitions).

6. **Edge cases & invariants**
   - The token length is **always** 44 characters; no trimming or length tolerance.
   - Verification uses Basic authentication with the token as the password (no additional encoding beyond what `net/http` does for the `Authorization` header when formatting `"Basic " + token`).
   - The detector should never panic or crash on malformed input; invalid regex matches that do not produce exactly one capture group should be discarded.


The URI detector regex fails to match passwords that contain special characters (e.g., `!#$%&()*+,-./;<=>?@[\]^_{|}~`). Update the regex to allow the full rfc3986 unreserved + sub-delims set in the authentication part, while still enforcing sensible length limits. Also ensure that identical raw credentials are not reported as duplicate findings.


Currently the Postgres detector is disabled because it matched loose ad-hoc credential lines (e.g., separate `POSTGRES_USER`, `POSTGRES_PASSWORD`) and produced too many false positives. Re-enable it, but restrict detection to only full connection URIs (`postgres://` or `postgresql://`). Update the keyword set to just `"postgres"`.

During verification, ignore `localhost` and loopback addresses by default to avoid scanning internal or test instances; allow a `detectLoopback` control flag for tests. Handle SSL negotiation robustly:

- Treat `sslmode=allow` or `sslmode=prefer` as unset (default to non-SSL fallback).
- Support the deprecated `requiressl` parameter by normalizing it to the corresponding `sslmode`.
- If the server returns a “database does not exist” error, the credentials are considered verified because authentication succeeded.
- Capture the final `sslmode` value in the result’s `ExtraData`.

Multi-host connection URIs are not required to be supported at this time. Re-enable the detector in the default detector list (previously commented out with a “Too noisy, needs attention” note).

Narrow Postgres detector to only look for URIs (#2314)


We need to add secrets detection for **Azure Blob Storage Shared Access Signature (SAS) tokens**. This detector should identify SAS tokens that appear alongside their corresponding storage account URL in source code, configuration files, logs, etc., and optionally verify whether the token is still live.

**Acceptance Criteria**

1. **Discovery**
   - Detect full Azure SAS credentials, consisting of a blob storage URL and a SAS token query string.
   - The URL must match the pattern `https://<storageaccount>.blob.core.windows.net/<container>[/<blob-path>]`, where `<storageaccount>` is a valid storage account name.
   - The SAS token must be a query string containing at least: `sp`, `st`, `se`, `sv`, `sr`, and `sig`. It may optionally contain `sip` (with an optional IP range) and `spr`.
   - The combined credential is reported as the URL + token (the exact byte sequences that were found).

2. **Classification**
   - The detector must classify the secret using the `AzureSasToken` detector type.

3. **Verification (live check)**
   - When verification is enabled, perform an HTTP GET to `{blob-url}?{sas-token}`.
   - **Success**: HTTP 200 → verified = true.
   - **Retry on mismatched signature**: If HTTP 403 response body contains `"Signature did not match"`, retry the request once by appending `&comp=list&restype=container` to the token (this handles container-level SAS tokens correctly).
   - **IP‑restricted tokens**: If HTTP 403 response body contains `"AuthorizationFailure"` and the token includes an `sip` parameter, treat the result as **unverified** with a verification error explaining the token is restricted to specific IP addresses.
   - **Host not found**: If the HTTP request fails with a “no such host” error, cache the storage account name as invalid so that no further verification attempts are made against that host during the same scan.
   - Any other response is considered unverified without marking the token as live.

4. **False positive suppression**
   - Implement custom false positive checking (even if initially a no‑op) so that the detector can be refined later without changing its public interface.

5. **Integration**
   - The new detector must be registered in the default detector list so it is automatically used by all scans that use the default configuration.

**Constraints & Edge Cases**
- Deduplicate identical URLs and tokens to avoid redundant verification requests.
- The regex for storage account names must respect Azure’s naming rules (3 to 24 characters, letters and numbers, no consecutive hyphens, etc.), but ultimate validation is done via the live check.
- Do not report a token if it is missing any mandatory parameter (`sp`, `st`, `se`, `sv`, `sr`, `sig`).
- The `sig` parameter may contain URL‑encoded characters (`%`‑encoded).

[Feat] Detector implementation for Azure SAS Tokens (#3963)


Fix SauceLabs credential detection:
- **Username pattern**: 2–70 characters of letters, digits, underscore, dot, or hyphen (`username` label prefix).
- **Access key pattern**: 8-4-4-4-12 UUID-like hex string (`saucelabs` label prefix).
- Optionally extract a SauceLabs API base URL from the code (e.g., `api.us-west-1.saucelabs.com`). If no base URL is found, fall back to `api.us-west-1.saucelabs.com`.
- Verify by sending HTTP Basic Auth `username:key` to `GET https://<baseURL>/team-management/v1/teams`.
  - Treat status `200` and `403` as verified (valid credentials).
  - Treat status `401` as invalid.
  - All other statuses indicate an error.
- Attempt all pairwise combinations of extracted usernames, keys, and base URLs when verifying.


The `ResultWithMetadata` structure currently carries only the decoded form of a chunk in the `ChunkData` field.  This breaks downstream consumers that require the original, pre-decode source bytes — for example, the secret‑storage encryption layer, which needs to operate on the exact data as it appeared in the source in order to correctly encrypt and later re‑identify stored secrets.  When iterative decoding transforms a chunk (e.g. unicode escaping, quoted-printable, base64), the original raw input is lost, and the encryption receives the decoded variant.  The result is that encrypted secret storage can no longer reliably re‑match or decrypt stored secrets because the “original” data it relies on has already been mutated.

**Expected behaviour:**
The engine should preserve the original chunk data before any decoding step begins and make it available in the `ResultWithMetadata` passed to the dispatcher.  The `ChunkData` field (or a dedicated new field) must always contain the raw bytes exactly as they were read from the source, irrespective of subsequent decode transforms.  When a chunk was not produced through the normal engine pipeline (e.g. in tests or ad‑hoc calls to `CopyMetadata`), the existing `Data` field should be used as a safe fallback to maintain backward compatibility.

**Impact:**
Secret‑storage encryption (and any other consumer that depends on the true original source data) is currently broken for any secret that was detected after a decode step.  The dispatcher receives decoded data, encrypts it, and later a re‑scan of the same source material cannot decrypt or match layers that were initially stored with the decoded form.  This leads to false negatives and inconsistent behaviour in enterprise secret‑storage workflows.  The fix must ensure that `OriginalData` is captured at the point the engine receives the chunk, threaded through all decoder layers (including `EscapedUnicode` which constructs new chunks), and finally attached to `ResultWithMetadata` by `CopyMetadata`.

Thread original chunk data through engine pipeline (#4780)


**Title:** Trim commas, semicolons, and whitespace from credential matches in common pattern helpers

**Context:**
`pkg/common/patterns.go` provides `UsernameRegexCheck` and `PasswordRegexCheck` helpers used by many detectors to extract credentials from config-like text. Currently, the `Matches` method trims spaces and a limited set of quote characters (`"' )`) from each match, but does not handle trailing commas or semicolons that often appear in structured config files (e.g., JSON, CSV, INI) or after a secret. This leaves unwanted punctuation attached to the extracted secret, causing unnecessary verification failures.

**Request:**
Enhance the string cleaning step inside `RegexState.Matches` so that after a match is found:
1. All leading/trailing whitespace is removed.
2. Trailing commas (`,`) and semicolons (`;`) are stripped, in addition to the existing quote and parentheses characters.
3. The cleanup logic remains safe for secrets that contain internal commas or semicolons (only trailing ones are removed).
4. The change applies automatically to all detectors using the standard username/password regex helpers (including Snowflake-specific patterns).

**Observable behavior:**
- Credentials extracted from strings like `"secret",` or `secret;` will now return `secret` instead of `secret,` or `secret;`.
- Whitespace-only padding is removed before punctuation stripping, so ` "secret" ,` still yields `secret`.
- Existing trimming of `'`, `"`, `)` is unaffected and preserved.

remove trailing commas and quotes from snowflake passwords (#4078)


**Feature Request: AWS Detector — Expose Credential Resource Type in Results**

As a user scanning for AWS secrets, I want to know immediately what kind of AWS resource a leaked key is associated with (e.g., long-term access key vs. temporary STS key) without needing to manually decode key prefixes.

**Acceptance Criteria**

1. **Resource type output** – When an AWS credential is detected and the key ID has one of the prefixes `AKIA`, `ABIA`, `ACCA`, or `ASIA` (the only prefixes currently matched by the detector), the result’s `ExtraData` map **must** include a key `“resource_type”` set to the corresponding human-readable label from the AWS documentation on unique identifiers:
   - `AKIA` → `“Access key”`
   - `ABIA` → `“AWS STS service bearer token”`
   - `ACCA` → `“Context-specific credential”`
   - `ASIA` → `“Temporary (AWS STS) access key IDs”`

2. **Always present** – The `“resource_type”` field **must** be populated for every detection result, regardless of whether the key is verified against the AWS API or not.

3. **Merged with verified data** – When verification succeeds, the pre-existing verified fields (e.g., `account`, `arn`, `user_id`, `rotation_guide`) **must** be combined with the new `“resource_type”` field so that all keys are available in the final `ExtraData`. The verified fields must not overwrite or remove `“resource_type”`.

4. **Backward compatibility** – The structure and behavior of all other result fields (`Raw`, `Redacted`, `Verified`, etc.) remain unchanged. Only the `ExtraData` map gains the additional key.

This allows users and downstream automation to immediately gauge the risk of an exposed credential (e.g., temporary STS tokens vs. long-lived access keys) and improve triage decisions.

added resource type mapping to extraData in AWS (#2087)


## Feature Request: Gemini API Credential Detector

### Context
TruffleHog currently lacks a detector for Gemini (cryptocurrency exchange) API keys and secrets. Gemini API credentials consist of an API key and a corresponding secret; the key may be either a master key (prefixed `master-`) or an account-level key (prefixed `account-`). When a master key is used, the account endpoint requires the `account` field to be set to `"primary"`. Secrets are alphanumeric strings of length 27-28 characters, while keys are the prefix + 20 alphanumeric characters. The API supports HMAC-SHA384 request signing for verification.

### Ask
Add a new detector for Gemini credentials that:

- **Detection**: Identifies candidate key-secret pairs in data using:
  - Key pattern: `\b((?:master-|account-)[0-9A-Za-z]{20})\b`
  - Secret pattern: `[A-Za-z0-9]{27,28}`
- **Verification**: For each candidate pair, construct a signed request to `https://api.gemini.com/v1/account` (POST with empty body, signed payload, and appropriate headers) and check that the response status is 2xx.
  - When the key starts with `master-`, set the JSON payload field `"account": "primary"`.
  - Compute the signature as HMAC-SHA384(secret, base64-encoded JSON of `{"request":"/v1/account","nonce":<current nanos>}`), hex-encoded.
- **Keywords**: Use `["master-", "account-"]` for pre-filtering.
- **Standard integration**: Implement the `detectors.Detector` interface, ensure the detector type enum `DetectorType_Gemini` is already present in the proto definitions, and register the scanner in `DefaultDetectors()`.

No other Gemini API endpoints should be called. Failed verification (non-2xx status) should flag the result as unverified, and false-positive filtering should be applied per the existing mechanism (`detectors.IsKnownFalsePositive`).


In `newDiff` (pkg/gitparse/gitparse.go), a default buffer-backed `contentWriter` is always created before the options are applied, even when `withCustomContentWriter` supplies its own writer. This causes a wasted allocation and initialization of the default writer. Modify `newDiff` so that the default writer is only instantiated if no custom `contentWriter` has been set after all options are processed.

[bug] - Refactor newDiff constructor to avoid double initialization of contentWriter (#2742)


We need to extend the Redis detector to handle connection strings that use TLS (`rediss://`) and those from Azure Redis Cache, which are not given as a standard URI. Currently, only `redis://...` URLs are recognized.

**Acceptance criteria:**

- The detector must match both `redis://` and `rediss://` style URLs in source data.
- It must also match Azure Redis connection strings that appear in a comma‑separated parameter form, like:
  `some-host.redis.cache.windows.net:6380,password=<secret>,ssl=True,abortConnect=False`
  (the host will always end with `.redis.cache.windows.net` and use port 6380; the password field contains the actual secret).
- When an Azure match is found, construct an equivalent `rediss://` URL (since SSL is mandatory) and use that for verification and for the reported raw/redacted output.
- To reduce noise:
  - Skip findings where the password consists entirely of `*` characters (this indicates the secret was already redacted in the source).
  - For unverified findings, discard those whose password starts with `$` (likely a variable that did not resolve).
- The existing redaction placeholder for a password should be seven `*` characters (to be consistent with other detectors and avoid unnecessary diffs).

The detector should still emit results with `DetectorType_Redis` and use the existing verification path (`verifyRedis` with TLS when appropriate). No breaking changes to the existing URI parsing and verification flow.

Detector-Competition-Fix: Fix redis to now support SSL, and look for azure redis connection strings (#1957)


Add a Stripe analyzer implementing `analyzers.Analyzer`. When a Stripe key is detected, the detector must pass the raw key in `AnalysisInfo` (key `"key"`). The analyzer should:

- Determine key type and environment via the existing `AnalyzePermissions` call.
- Produce an `AnalyzerResult` with:
  - `Metadata` containing `key_type` and `key_env`.
  - Each permission category as a parent `Resource` (type `"category"`), with `Bindings` mapping each of the category's permissions to a `Permission` whose `Value` is the permission name and `AccessLevel` is taken from the permission's `*Value`.
  - Any permission category that has zero permissions must appear in `UnboundedResources` (using the same parent resource).
- The analyzer type must be `Stripe`.


Add `Role` field to S3 legacy-scan checkpoint; only apply `startAfter` when role matches the checkpoint on resume.

Currently, the checkpointer stores only `CurrentBucket` and `StartAfter` in the `EncodedResumeInfo`. When a scan with multiple roles is interrupted, a resume can pick a different role but still reuse the saved key offset, causing objects to be skipped. The resume checkpoint must include the role ARN, and on resume the `startAfter` offset must only be used when the current scan’s role equals the checkpoint’s role.

### Behavior requirements
- `Checkpointer` checkpoint and resume point must include the role.
- `determineResumePosition` must extract the role from the resume point into `resumePosition`.
- In `scanBuckets`, when iterating roles and buckets, set `startAfter` **only if** the current bucket equals `pos.bucket` **and** the current role equals `pos.role`.
- If role is empty (e.g., no roles configured), behavior must be unchanged and match empty‑role checkpoints.
- When decoding a legacy checkpoint that lacks a role, treat the missing role as an empty string so the scan still resumes from the saved offset for role-less scans and doesn’t break.

[INS-207] Add Role-Aware Resumption Support for Legacy S3 Scan (#4600)


**Title:** Atlassian detector: extract organization ID and include it in AnalysisInfo for verified keys

**Background**
Currently the Atlassian detector only extracts the API key and, upon verification, populates the `Organization` name from the API response. The analysis engine can perform deeper inspection (e.g., enumerating Jira projects, Confluence spaces) but requires both the key **and** the organization ID (a UUID) to target the correct org. Organization IDs often appear alongside keys in logs, configuration, or documentation. We need to capture this ID when it is present in the scanned data and deliver it to the analysis engine.

**Request**
Enhance the Atlassian detector (`v2`) to:

1. **Detect organization IDs** – Search the same data chunk for UUIDs that are explicitly labeled as an organization ID. Use a pattern that requires a prefix keyword like `org` or `id` (case-insensitive) before the UUID to reduce false positives. A valid organization ID follows the pattern `[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`. (e.g., `org id 123e4567-e89b-12d3-a456-426614174000`)

2. **Correlate findings** – For every Atlassian key detected, if one or more organization IDs are also found in the same chunk, each unique ID should generate a separate result with the same key, so that analysis can be attempted against each organization. If no organization ID is found, the key should still be reported and the analysis info should contain only the key (without an `organization_id` field).

3. **Pass to AnalysisInfo** – When the key is verified, populate `AnalysisInfo` with:
   - `key` → the raw Atlassian token string
   - `organization_id` → the organization UUID (only when an ID was extracted)

   This must happen **only** for successfully verified keys. Unverified keys or keys that fail verification should not carry `AnalysisInfo`.

**Acceptance Criteria**
- Organization IDs matching the described pattern (with a keyword prefix) are collected alongside API keys.
- Verified results include `AnalysisInfo` containing at least `"key"`, and `"organization_id"` when an ID was extracted from the same chunk.
- When no organization ID is present, verified results still contain `AnalysisInfo` with `"key"` and no `"organization_id"`.
- The existing verification flow (including the `Organization` extra data and error handling) remains unchanged.
- No regressions in key detection accuracy or verification logic.

Detect Organization ID to pass into AnalysisInfo for Atlassian Detector (#4480)


**Title:** Remove progress tracking and resume support from the GCS source

**Description**
The GCS source currently maintains detailed per‑bucket/per‑object progress information (encoded resume info, sections completed, percent complete) to support resuming interrupted scans. This adds significant complexity and has proven to be unreliable. We want to eliminate all progress tracking for the GCS source so that every scan starts from a clean slate and no resume capability is provided.

**Acceptance Criteria**

* When a scan of a GCS source completes, the `Progress` fields (e.g., `PercentComplete`, `SectionsCompleted`, `SectionsRemaining`) are not updated – they remain at their zero values throughout the scan.
* The `EncodedResumeInfo` field is never set or modified by the GCS source.
* No resume data is read from `EncodedResumeInfo` when initializing a scan; the initialization logic ignores any previously stored encoded resume info and does **not** compute bucket start offsets from it.
* The source performs a full, unconditional enumeration of all matching buckets and objects every time it starts, without skipping objects based on prior progress.
* The source no longer uses a lock (e.g., `sync.Mutex`) to serialize progress updates; there is no progress‑related shared mutable state.
* The `Chunks` method does not call any progress‑recording functions (i.e., it does not mark objects as “processing” or “processed”).
* The internal structures previously used for progress bookkeeping (bucket‑object mappings, processing/processed sets, objects progress counters) are removed – they must not influence the scan flow or memory consumption.

**Non‑Goal**
This request does *not* prescribe how to reorganize the initialization steps (e.g., extracting a helper function), only that the progress‑tracking behavior be completely deleted. Any incidental refactoring that accompanies the removal is acceptable as long as it preserves the observable behavior described above.

Delete progress tracking from GCS source (#1190)


Tighten the slackwebhook detector’s verification logic for HTTP 400 responses.
Currently, the detector uses substring matching (`strings.Contains`) to check if the body contains `"no_text"` or `"missing_text"`.
This causes false positives, for example when Slack returns a 400 body like `"missing_text_or_fallback_or_attachments"` for a disabled account.

Change the check to exact byte comparison so that only a response body that is exactly `"no_text"` or exactly `"missing_text"` is treated as a verified secret.
All other 400 responses should leave the secret as unverified.
The success-path verification (status 2xx) remains unchanged.

[chore] - fix slackwebhook detector (#1761)


## Feature Request

**Title:** Support detecting Datadog API keys without an accompanying application key

**Area:** `pkg/detectors/datadogtoken` detector

**Current Behavior:**
The Datadog token detector only reports secrets when a Datadog _application_ key (40-char pattern) and a Datadog _API_ key (32-char pattern) are found together in the same scan chunk. If only an API key is present, it is ignored.

**Desired Behavior:**
Standalone Datadog API keys should also be detected and verified. Datadog API keys are usable without application keys for many API operations, so a plain API key is still a valid credential that should be flagged.

**Acceptance Criteria:**

1. **Detect standalone API keys:**
   When a 32‑character Datadog API key is found in the scanned data without a nearby 40‑character application key, produce a finding with the API key as the raw secret.

2. **Verification for API-key-only findings:**
   For verification of a standalone API key, use the Datadog API key validation endpoint (`GET /api/v1/validate`) with the `DD-API-KEY` header only. The finding’s `Verified` field should be `true` when the response status is 2xx, and `false` otherwise. The existing `IsKnownFalsePositive` check must be applied to the API key to reduce noise.

3. **No duplication:**
   When _both_ an API key and an application key are found together (in any order), the detector should continue to produce only one combined finding (using both keys for verification, as it does today). It should not produce an additional API‑key‑only finding for the same key. The combined finding must preserve its existing headers (`DD-API-KEY` + `DD-APPLICATION-KEY`) and verification logic.

4. **Metadata:**
   The `ExtraData` of each finding should indicate whether it is an `“Application+APIKey”` combination or an `“APIKeyOnly”` detection, to help users understand the secret’s context.

**Edge Cases / Safety:**
- If a chunk contains multiple API keys with no app keys, each should be reported individually.
- If a chunk contains multiple API keys and some have associated app keys while others do not, the ones without an app key should be reported as standalone API key findings.
- Ensure the new regex matching does not change the existing false‑positive reduction behavior (boundary characters, keyword pre‑screening, etc.).

Detect API keys without app keys (#1605)


We need to unify the way JDBC connection strings are parsed and represented internally, so that the parsed details can be reused by the upcoming credential analyzers (INS-170). Currently the parsing functions for MySQL, PostgreSQL, and SQL Server are unexported, and each database handler stores the extracted components in its own ad-hoc way:

- MySQL stores user+password as a single `userPass` string and host as a separate field, with no explicit database field.
- PostgreSQL dumps everything into a `params` map, including `host`, `user`, `password`, and `dbname`.
- SQL Server builds a full `sqlserver://` URL string and discards much of the original structured information.

For analyzers we need a uniform way to access the host, database name, user, password, and any additional query parameters from the parsed JDBC URL.

**Requirements:**

1. Export the parsing functions for MySQL, PostgreSQL, and SQL Server so analyzers can import them.
2. Introduce a shared, exported `ConnectionInfo` struct with fields:
   - `Host` (host:port if port is specified)
   - `Database`
   - `User`
   - `Password`
   - `Params` (map of additional query parameters, e.g., `sslmode`, `connect_timeout`, etc.)
3. Refactor the per‑database structs (`mysqlJDBC`, `postgresJDBC`, `sqlServerJDBC`) to embed this new `ConnectionInfo` struct and store parsed values directly in its fields, rather than in a single `conn` string or a generic `params` map.
4. Ensure that PostgreSQL’s parsed user and password are stored in the `User` and `Password` fields, not only in the `Params` map. The `Params` map should still retain any extra parameters (e.g., `sslmode`).
5. The detection pings must continue to work exactly as before, including:
   - PostgreSQL’s ping logic which falls back to a default user `"postgres"` when no user is provided.
   - MySQL’s default database `mysql` when none is parsed.
   - SQL Server’s default port `1433`, user `sa`, and database `master`.
   - All connection timeouts (`5s` for MySQL & PostgreSQL, `connection+timeout=5` for SQL Server) remain unchanged.
6. The building of final connection strings (e.g., `BuildMySQLConnectionString`, `BuildPostgresConnectionString`, `BuildSQLServerConnectionString`) must use the new `ConnectionInfo` fields, not the old internal representations.

**Non‑obvious constraints:**
- Host may include a port; splitting or formatting for the underlying drivers must remain correct (e.g., `tcp(host:port)` for MySQL, separate `host` and `port` in PostgreSQL connection strings when a port is present).
- If no user is specified in the JDBC URL, the `User` field should be empty; the default user logic lives in the connection‑string builders, not in the parsers.
- The existing behavior where invalid URLs (missing host or password) are rejected with an error must be preserved.

[INS-170] Unify JDBC URL parsing across detectors and analyzers (#4574)


**Title:** Make job progress metrics JSON‑serializable and thread‑safe for external consumers

**Background**
`JobProgressMetrics`, `UnitMetrics`, and the `JobProgressRef` structs are used internally to track source‑scanning progress through hooks. These types currently lack JSON tags, making it impossible to expose them in a structured way (e.g., for a status API or structured logging). Additionally, time fields are stored as `time.Time` values, where the zero‑value is ambiguous: we cannot distinguish “not started” from “started at epoch”.

**Request**

1. **Add JSON struct tags** to all exported fields of `JobProgressRef`, `JobProgressMetrics`, and `UnitMetrics`.
   - Use `omitempty` on fields that may reasonably be absent (e.g., optional time fields, empty slices).
   - Time fields should serialize in RFC‑3339 format.

2. **Change `StartTime`/`EndTime` fields from `time.Time` to `*time.Time`** in both `JobProgressMetrics` and `UnitMetrics`.
   - A `nil` pointer means “not set”, removing the ambiguity of zero‑time.
   - All existing logic that checks `IsZero()` on these fields must be updated to check for `nil` instead, and dereference safely where a concrete time is needed (e.g., `ElapsedTime()`, `IsFinished()`).

3. **Thread‑safe snapshot**
   The `JobProgress.Snapshot()` method must return a **read‑only copy** of the metrics so that concurrent readers cannot observe the original mutable time pointers. It should deep‑copy the `*time.Time` values (not just the pointers) to avoid external mutation accidentally changing internal state. This ensures that callers who receive a snapshot can safely use the returned `JobProgressMetrics` without races.

**Acceptance criteria**
- All progress‑related structs marshal to valid JSON with field names in snake_case.
- `StartTime` and `EndTime` are omitted from JSON output when not set (i.e., when the pointer is `nil`).
- `ElapsedTime()`, `IsFinished()`, and `PercentComplete()` continue to work correctly with the new pointer semantics.
- `Snapshot()` returns a metrics struct whose time fields point to **different** `time.Time` values than the internal metrics, so modifications to the snapshot do not affect the original.
- Existing callers of `Snapshot()` and the progress hooks (e.g., `UnitHook`, `SourceManager`) continue to compile and behave as before.

[chore] Add JSON tags to job metrics (#2114)


## Bug: Slack detector discards secrets when verification encounters any error

**Observed behavior**
When scanning for Slack tokens, the `Scanner.FromData` method currently only emits a `Result` if the `auth.test` API call returns `{"ok":true}`.
If the HTTP request itself fails (e.g. timeout, DNS error, non‑200 response), if the JSON body cannot be decoded, or if the API returns `{"ok":false}` with *any* error (including transient or surprising errors like `"ratelimited"`), the token is silently dropped and no `Result` appears in the output.
This means that tokens that are potentially valid but could not be verified due to network issues are completely invisible to the user. Tokens that are *definitely invalid only* in the standard `"invalid_auth"` case are also dropped, but there is no distinction between that deterministic invalidation and an indeterminate outcome.

**Expected behavior**
The detector should implement tri‑state verification and always return a `Result` for every matched token. Specifically:

1. **Successful verification** (`"ok":true`) → `Result.Verified = true`, no error.
2. **Deterministically unverified** (`"ok":false, "error":"invalid_auth"`) → `Result.Verified = false`, no `VerificationError`. The token is known to be bad.
3. **Indeterminate / unexpected failure** (network error, HTTP error, JSON parse failure, API response with `"ok":false` and an error other than `"invalid_auth"`) → `Result.Verified = false`, **and** `Result.VerificationError` should be set to a descriptive error. This preserves the result so users can manually investigate or observe patterns of transient failures.

4. Before adding a result that is not verified, the scanner should consult the standard false‑positive detection (`detectors.IsKnownFalsePositive`) and discard the result if it matches a known non‑secret pattern, to keep noise low.

**Impact**
- Secrets that could have been detected are silently missed, especially in automated CI/CD pipelines where occasional network issues are common.
- Operators have no visibility into whether a scan “just didn’t find anything” vs. “verification was impossible for some results”.
- The current behavior hides potential AWS/IP‑restricted tokens that might be valid from a different environment.
- Lack of tri‑state verification also makes it impossible to test the detector’s behavior under error conditions.

**Non‑obvious constraints**
- The `auth.test` endpoint returns HTTP 200 even for invalid tokens; the only reliable signal is the `"error"` field in the JSON body. Only `"invalid_auth"` should be treated as a definitive negative; any other error string (including future unknowns) must be considered indeterminate.
- To allow reliable testing of timeouts and error responses, the scanner should support injecting an `*http.Client` (e.g. via a field on the `Scanner` struct). If no client is provided, the existing `common.SaneHttpClient()` default should be used.

**Acceptance criteria (behavioral)**
- Scanning a string containing a valid Slack token that triggers a network timeout (or a URL that returns an unexpected error response) produces a `Result` with `Verified == false` and a non‑nil `VerificationError`.
- Scanning a token that returns `"ok":false, "error":"invalid_auth"` produces a `Result` with `Verified == false` and `VerificationError` == nil.
- Scanning a known false positive token (unverified) results in no `Result` being emitted.
- Existing correctly‑verified tokens are still reported exactly as before.

add tri state verification to slack (not slack webhook) (#1731)


---
**Title:** Windows: scanning local repo via `file://` URI fails to locate staged-changes index

**Summary**
When scanning a local Git repository on Windows with a `file://` URI (e.g., `file:///C:/Users/me/project`), TruffleHog cannot find the `.git/index` file and therefore silently skips all staged-but-uncommitted changes. This leaves live secrets staged in the working tree undetected.

**Steps to reproduce**
1. On Windows, create a Git repository with a staged change containing a credential.
2. Run TruffleHog against the repository using a `file://` URI.
3. Observe that the staged credential is not reported, while a committed credential *is* detected.

**Expected behavior**
- The file-URI normalization should produce a path with forward slashes that can be joined with the Git dir and `index` filename to correctly locate the index on Windows.
- The index path lookup inside `PrepareRepo` should be derived from the `Path` field of the normalized URL, not by stripping the `file://` prefix from the string representation, which is fragile with Windows drive letters and backslashes.

**Observed behavior**
- `normalizeFileURI` leaves backslashes in the returned path when running on Windows.
- `PrepareRepo` constructs the original index path by trimming `file://` from the URL string, which yields an invalid path when drive letters and backslashes are present (e.g., `file://C:%5CUsers%5Cme%5Cproject` → `C:%5CUsers%5Cme%5Cproject`).
- The subsequent `os.ReadFile` call fails silently, and the staged changes are never scanned.

**Impact**
Any secret that has been `git add`-ed but not yet committed is missed on Windows, creating a significant false-negative risk for users scanning local working directories.

Fix/issue 4578 path normalization for unix and windows (#4614)


Scan Docker image history for secrets in addition to layers.

Currently, only the layer fileystems are scanned. The image configuration history (the `created_by` field of each non-empty history entry) often contains secrets from `RUN` commands, build-args, and similar. Without scanning it, such secrets are missed.

### Required behavior
- For each image history entry where `empty_layer` is false, treat the `created_by` text as a chunk to be scanned.
- Where possible, associate the history entry with the digest of its corresponding layer (next non-empty layer in sequence), and include that digest in the metadata for traceability. If no layer corresponds, the entry should still be scanned but without a layer digest.
- The source metadata for these chunks must clearly distinguish them from layer file chunks. Use a path-like identifier, e.g., `image-metadata:history:<index>:created-by`.
- Do not change how layer file chunks are produced or labeled.
- Add a new Prometheus metric for the number of history entries scanned (similar to `docker_layers_scanned`).

### Non-goals
- No need to scan history that represents empty layers, as there is no text content.
- No changes to the layer scanning logic or metadata format for existing layer chunks.

### Why
Build commands frequently contain credentials passed via environment variables, `--build-arg`, or inline URLs that never appear on the filesystem. Scanning history catches these.

feat: support docker image history scanning (#2882)


**Title:** Add per‑file processing timeout and expose file metadata during handling

**Description**

When TruffleHog processes input files, there is no guaranteed time limit on individual file handling, and the handler does not log the file’s size or MIME type. This makes it hard to diagnose slow or stuck processing and to correlate logging with the file being processed.

**Acceptance Criteria**

1. **Per‑file timeout**
   - The `HandleFile` function must apply a context timeout of 30 seconds (configurable via `SetArchiveMaxTimeout`) to the entire file handling pipeline—from handler selection through chunk reporting—for every file type.
   - The timeout must be enforced uniformly; individual handlers (e.g., the archive handler) must not manage their own timeout separately. The archive handler’s `HandleFile` must rely on the context timeout passed from `HandleFile`.

2. **File metadata in context**
   - Immediately after the `fileReader` is created and before any handler runs, the effective context used for processing must be decorated with the file’s MIME type (under the key `"mime"`) and the file’s total size in bytes (under `"size_bytes"`).
   - This metadata must be available for all logging done during file processing.

3. **Expose file size from `BufferedReadSeeker`**
   - The `BufferedReadSeeker` must provide a `Size() (int64, error)` method that returns the total size of the underlying reader.
   - The method must work correctly for:
     - Seekable readers (e.g., `*strings.Reader`, files)
     - Non‑seekable readers (e.g., `*bytes.Buffer`, network streams)
     - Empty readers
     - Readers that have been partially read before calling `Size`
   - Calling `Size` must not alter the logical read position; after the call, subsequent reads and seeks must continue from the same position as before.
   - If the size cannot be determined (e.g., an error occurs while scanning), an error must be returned.

**Notes / Constraints**
- The timeout applies to all handlers, not only archive processing. Previous behavior limited the timeout to an inner goroutine inside the archive handler; this must be lifted to `HandleFile` to protect against hangs in any handler.
- The `Size` method must be efficient; caching is acceptable once the total size is known.

[fix] - Add Size Method to BufferedReadSeeker and Refactor Context Timeout Handling in HandleFile (#3307)


`FragmentLineOffset` currently only identifies secrets that fit within a single line of chunk data. Multi-line raw results (secrets that span newlines) are not handled and return a false `found` indicator. Update `FragmentLineOffset` to:

- Locate the first occurrence of the full raw result within the chunk, even if it spans multiple lines.
- Derive the line number from the start position (count newlines before the match).
- Check the **ending** line of the secret (the line containing the last byte of the raw result) for a `trufflehog:ignore` tag; only ignore when that tag appears on the final line of the secret.
- Handle the edge case where the secret ends at EOF (no trailing newline) for the ignore-tag check.
- Preserve or improve scanning performance compared to the current line-by-line scan.

Refactor FragmentLineOffset to match multiline secrets (#1612)


As a user of the GitLab source, I need a way to validate my configuration before running a live scan, so I can catch misconfigurations early and avoid incomplete or failed scans. The validation should be a lightweight, non-scanning operation that returns a list of clear, human-readable errors when things are wrong.

**Acceptance Criteria**

- The GitLab source must implement the `Validator` interface, making it possible to call a dedicated validation method.
- If authentication fails (e.g., invalid token, wrong scope, bad credentials), validation must report an error.
- For each explicitly configured repository URL, validation must:
  - Check that the URL can be normalized (no gobbledygook, correct scheme, etc.).
  - Verify that the repository is reachable using the configured credentials, without cloning the entire repo. It is acceptable to use a lightweight `git ls-remote` check against a non-existent ref.
  - Report a distinct error for each unreachable or malformed repository URL.
- When the user provides both an explicit repository list **and** ignore patterns (`ignoreRepos`), validation must warn that ignore patterns will be ignored for this configuration – they are only meaningful when no repositories are listed.
- If no repositories are explicitly listed, validation must:
  - Use the GitLab API to discover all projects visible to the authenticated user/credential.
  - Apply the configured ignore patterns (if any) and determine whether any project remains to be scanned.
  - If every discovered project is excluded by the ignore patterns, validation must report an error indicating that no repositories would be scanned.
- Any invalid ignore pattern globs (e.g., syntactically malformed expressions) must be detected and reported as configuration errors during validation.
- Validation must not clone repositories or perform any heavyweight operations; it should only use API calls and Git Ls-Remote checks.

**Out of Scope**
- Validation of other source types (GitHub, S3, etc.).
- Scanning or producing any chunks.
- Automatic retry on transient network errors – the first failure should be reported.

Implement Gitlab source validation (#1765)


Below is a repository-specific implementation request written as a behavior-first acceptance criteria description, fitting the patch’s intent and context.

---

**Title:** GitLab detectors should correctly iterate over multiple endpoints and emit endpoint-bound results

**Background**
Both GitLab detector versions (`v1` for legacy tokens and `v2` for `glpat-` prefixed tokens) support verification against multiple endpoints (e.g., custom self-hosted instances or the public SaaS endpoint). Currently, when more than one endpoint is configured, the verification loop overwrites the same result object, so only the outcome from the last endpoint is reported. This can hide a successful verification that happened earlier, and it makes it impossible to know which endpoint validated the secret.

**Expected Behavior**

1. **Endpoint-specific results**
   For each detected secret, the detector must produce a **separate result per endpoint** that was tested. This applies regardless of whether verification is enabled.

2. **Result → endpoint association**
   Each `Result` must have its `RawV2` field set to the **secret string immediately followed by the endpoint URL** (e.g., `mysecrethttps://gitlab.example.com`). This allows downstream consumers to identify exactly which endpoint was used.

3. **Stop on first successful verification**
   When verification is `true`, the detector must attempt each configured endpoint **in order**. As soon as one endpoint **successfully verifies** the secret, the detector must:
   - Emit the verified result (with that endpoint's URL in `RawV2`),
   - **Not** attempt any remaining endpoints for that secret (to avoid redundant requests and duplicate positives),
   - Still include in the results any **earlier** unverified results from endpoints that failed before the successful one.

4. **No verification mode**
   When `verify` is `false`, the detector must still emit one result per configured endpoint, each carrying the `RawV2` as described, and all with `Verified = false`.

5. **`glpat-` exclusion remains**
   The v1 detector must **continue ignoring** secrets that contain the `glpat-` prefix (these are handled by the v2 detector), preserving the existing false-positive avoidance.

**Edge Cases & Constraints**
- Only configured endpoints that are actually used via `UseCloudEndpoint` (and any additional explicitly set endpoints) should be iterated; the `Endpoints()` method of the `EndpointCustomizer` interface already provides the final list.
- The behavior described here applies to both `v1` and `v2` GitLab detector implementations consistently.
- The verification function `VerifyGitlab` already accepts an endpoint parameter; no change to its signature is required.

**Benefit**
This change ensures that when scanning repositories or other sources, valid GitLab secrets are accurately associated with the instance they belong to, and scanning against both self-hosted and SaaS endpoints yields clear, non-overwritten results.


**Title:** Fix case-sensitivity scope in `PrefixRegex` and increase allowed gap to 40 chars

**Context:**
The `PrefixRegex` helper in `pkg/detectors/detectors.go` builds a regex fragment that ensures at least one keyword appears within some distance before the secret‑capturing group. It is used widely by secret detectors to reduce false positives.

Currently the function wraps keywords in `(?i)(?:keyword1|...|keywordN)`. Because `(?i)` is an un‑scoped flag, it makes the *entire remainder of the detector’s regex* case‑insensitive – including the secret capture group that is appended later. This can cause false negatives for secrets whose format is strictly case‑sensitive, and may also widen matches unintentionally.

**Requested change:**

1. Scope case‑insensitivity **only** to the keyword alternatives, leaving the capture group (and any trailing parts of the detector regex) case‑sensitive.
   - The replacement should use a non‑capturing group with a local flag: `(?i:keyword1|...|keywordN)`.

2. Bump the allowed separation from 20 to **40 characters** to accommodate more realistic real‑world patterns where the secret appears further from its keyword.

**Acceptance criteria:**
- Detectors that call `PrefixRegex` match the same keywords case‑insensitively.
- The appended capture group matches with its original case‑sensitivity.
- The preceding context can be up to 40 characters (including newlines).
- No existing tests are broken; false-positive/negative rates for affected detectors should be equal or improved.

[bug] - Fix case-sensitivity issue in PrefixRegex function (#2811)


# Add a detector for Grafana Cloud API keys

We need a new detector that can find leaked Grafana Cloud API keys.
Grafana Cloud API keys are prefixed with `glc_` and look like base‑64 strings (with optional padding `=`).
They are used to authenticate against Grafana Cloud APIs, e.g. `https://grafana.com/api/v1/tokens`.

## Desired behaviour

- **Detection**
  Match strings starting with `glc_`, composed of alphanumerics, `+`, `/`, and possibly `=` (length ~50–150 characters).
  Use a keyword pre‑filter: `"glc_"`.

- **Verification** (optional, when `--verify` is enabled)
  Send a `GET` request to `https://grafana.com/api/v1/tokens?region=us` with the header `Authorization: Bearer <secret>`.
  - `2xx` **or** `403` → verified.
    (A valid key that lacks permission to list tokens may return 403; that should still be treated as a true positive.)
  - `401` → definitely **not** verified (invalid/revoked key).
  - Any other status code (e.g. `404`) → treat as a verification *error* (unexpected API surface / endpoint behaviour).
  - Network errors → propagate as verification failure (not a hard error).

- **False‑positive exclusion**
  After verification fails, run the secret through `detectors.IsKnownFalsePositive` with the default false‑positive list (and randomness check enabled). Keys that match known false positives or appear insufficiently random should be discarded.

- **Registration**
  - Add a new `DetectorType_Grafana` enumerator to the proto definition (and regenerate the pb files).
  - Register the scanner in `DefaultDetectors`.

- **Keywords**
  The detector’s `Keywords()` method must return `["glc_"]` for efficient chunk pre‑filtering.

## Constraints & edge cases

- The regex should be bounded to reduce false positives; use `\b` anchors.
- When the HTTP client is not provided, fall back to `common.SaneHttpClient()`.
- Do **not** leak raw secrets in error messages/logs.
- The detector must implement the `detectors.Detector` interface (compile‑time check `var _ detectors.Detector = (*Scanner)(nil)`).

This will let users automatically catch leaked Grafana Cloud API keys in git history, repos, filesystems, etc., closing a gap in our coverage.

Detector-Competition-New - Created Grafana Cloud API Key detector  (#1959)