Option C Tools
Loading...
Searching...
No Matches
secret_scanner Namespace Reference

Functions

bool _name_matches_secret (str name, *, str mode="substring")
bool _is_string_literal (ast.expr node)
tuple[bool, str] _is_string_like (ast.expr node)
float _shannon_entropy (str s)
set[int] _collect_docstring_node_ids (ast.Module tree)
set[int] _collect_keyword_excluded_node_ids (ast.Module tree, list[str] field_patterns)
list[tuple[int, str, float]] _check_entropy_violations (ast.Module tree, *, float threshold=_ENTROPY_DEFAULT_THRESHOLD, float docstring_threshold=_DOCSTRING_ENTROPY_DEFAULT, float comment_threshold=_COMMENT_ENTROPY_DEFAULT, int min_length=_ENTROPY_MIN_LENGTH, frozenset[int] excluded_node_ids=frozenset())
tuple[bool, str] check_no_hardcoded_secrets (str text, ast.Module|None tree=None, *, str mode="substring", float code_entropy_threshold=_ENTROPY_DEFAULT_THRESHOLD, float docstring_entropy_threshold=_DOCSTRING_ENTROPY_DEFAULT, float comment_entropy_threshold=_COMMENT_ENTROPY_DEFAULT, list[str]|None excluded_field_patterns=None)

Variables

 _CAMEL_SPLIT_RE = re.compile(r"(?<=[a-z0-9])(?=[A-Z])")
dict _SECRET_NAME_PATTERNS
float _ENTROPY_DEFAULT_THRESHOLD = 4.5
float _DOCSTRING_ENTROPY_DEFAULT = 5.0
float _COMMENT_ENTROPY_DEFAULT = 5.0
int _ENTROPY_MIN_LENGTH = 16

Detailed Description

Purpose
-------
OI-514 — standalone hardcoded-secret detection. Extracted from
:mod:`oct.linter.oct_lint` so :mod:`oct.tools.source_exporter`,
:mod:`oct.git.quality_gate`, and any future AI review tools no longer
need to import the linter just to reach the scanner.

The linter continues to re-export the same names for backwards
compatibility — call sites that already import them from
``oct.linter.oct_lint`` keep working unchanged.

Responsibilities
----------------
- Host :data:`_SECRET_NAME_PATTERNS` — the canonical set of substrings
  that flag a variable/parameter/key name as "secret-shaped".
- Provide :func:`_name_matches_secret` for substring matching.
- Provide :func:`check_no_hardcoded_secrets` — the AST-based scanner.
- Provide :func:`_is_string_literal` / :func:`_is_string_like` helpers.

Diagnostics
-----------
Domain: SECRET-SCAN
Levels:
    L2 — scan lifecycle (start / end)
    L3 — pattern hits (per-line matches)
    L4 — AST walk details

Contracts
---------
- Must not import :mod:`oct.linter.oct_lint` (no circular dependency).
- Function signatures and return shapes are stable — the linter
  re-exports them, so any change here is a breaking change there.
- Name patterns are case-insensitive substring matches.

Function Documentation

◆ _check_entropy_violations()

list[tuple[int, str, float]] secret_scanner._check_entropy_violations ( ast.Module tree,
* ,
float threshold = _ENTROPY_DEFAULT_THRESHOLD,
float docstring_threshold = _DOCSTRING_ENTROPY_DEFAULT,
float comment_threshold = _COMMENT_ENTROPY_DEFAULT,
int min_length = _ENTROPY_MIN_LENGTH,
frozenset[int] excluded_node_ids = frozenset() )
protected
Walk AST for string constants exceeding the entropy threshold.

Returns a list of ``(lineno, value_preview, entropy_bits)`` tuples.
Only strings longer than *min_length* are checked.  The preview is
truncated to 24 characters for readability.

Code strings use ``>=`` (catches at threshold).  Docstrings use
strict ``>`` (must exceed threshold).  ``comment_threshold`` is
accepted for forward compatibility but not yet consumed — comments
do not appear in the AST.

Nodes whose ``id()`` is in *excluded_node_ids* are skipped entirely
(field-name exclusions from octrc ``entropy_exclusions``).

Definition at line 192 of file secret_scanner.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _collect_docstring_node_ids()

set[int] secret_scanner._collect_docstring_node_ids ( ast.Module tree)
protected
Return ``id()`` values of ast.Constant nodes that are docstrings.

Definition at line 153 of file secret_scanner.py.

Here is the caller graph for this function:

◆ _collect_keyword_excluded_node_ids()

set[int] secret_scanner._collect_keyword_excluded_node_ids ( ast.Module tree,
list[str] field_patterns )
protected
Return ``id()`` values of ast.Constant nodes that are keyword
arguments with names matching *field_patterns*.

This identifies strings like ``correct_pattern="..."`` in
``RuleInfo(correct_pattern="...")`` calls — documentation/example
fields in namedtuple/dataclass constructors.

Definition at line 167 of file secret_scanner.py.

Here is the caller graph for this function:

◆ _is_string_like()

tuple[bool, str] secret_scanner._is_string_like ( ast.expr node)
protected
Return ``(is_string_like, kind)`` for use by :func:`check_no_hardcoded_secrets`.

``kind`` is one of ``"literal"``, ``"f-string"``, or ``"concatenation"``
(OI-408). Extended detection catches f-strings and ``"a" + "b"`` patterns
in addition to plain string constants.

Definition at line 111 of file secret_scanner.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _is_string_literal()

bool secret_scanner._is_string_literal ( ast.expr node)
protected
True when the AST node is a string constant (hardcoded literal).

Definition at line 106 of file secret_scanner.py.

Here is the caller graph for this function:

◆ _name_matches_secret()

bool secret_scanner._name_matches_secret ( str name,
* ,
str mode = "substring" )
protected
Return True when ``name`` looks secret-shaped.

OI-529: two matching strategies are supported.

``mode="substring"`` (default, back-compat) — any pattern that
appears anywhere inside ``name.lower()`` flags the name. This is
strict but noisy: ``tokenizer`` (contains ``token``), ``pwdistance``
(contains ``pwd``), etc. all flag.

``mode="word"`` — camelCase boundaries are converted to underscores
(``apiKey`` → ``api_Key``), the name is lowercased, and each pattern
must appear delimited by start-of-string, end-of-string, ``_``, or
``-`` — so compound patterns like ``api_key`` still match when the
original was ``apiKey`` or ``api-key``. Whole-segment hits still
flag (``user_password``) but false positives like ``tokenizer`` and
``pwdistance`` pass clean.

Definition at line 75 of file secret_scanner.py.

Here is the caller graph for this function:

◆ _shannon_entropy()

float secret_scanner._shannon_entropy ( str s)
protected
Return the Shannon entropy (bits per character) of *s*.

H = -sum(p * log2(p)) for each unique character's frequency p.
Returns 0.0 for empty strings.

Definition at line 140 of file secret_scanner.py.

Here is the caller graph for this function:

◆ check_no_hardcoded_secrets()

tuple[bool, str] secret_scanner.check_no_hardcoded_secrets ( str text,
ast.Module | None tree = None,
* ,
str mode = "substring",
float code_entropy_threshold = _ENTROPY_DEFAULT_THRESHOLD,
float docstring_entropy_threshold = _DOCSTRING_ENTROPY_DEFAULT,
float comment_entropy_threshold = _COMMENT_ENTROPY_DEFAULT,
list[str] | None excluded_field_patterns = None )
Detect string literals bound to secret-shaped names and high-entropy strings.

Inspects three AST shapes for name-based detection:

* Direct/annotated assignments — ``password = "literal"``.
* Dict literals — ``{"api_key": "literal"}``.
* Function default arguments — ``def f(token="literal"): ...``.

Safe patterns are ignored — calls, subscripts, env lookups.

FS-507: when *code_entropy_threshold* > 0, also flags any string literal
whose Shannon entropy exceeds the threshold (default 4.5 bits/char).
Code strings use ``>=``; docstrings use strict ``>``.
Set to 0 to disable entropy checking.

*docstring_entropy_threshold* (default 5.0) sets the bar for
docstring constants.  *comment_entropy_threshold* (default 5.0) is
accepted for forward compatibility but not yet consumed.

OI-529: ``mode`` forwards to :func:`_name_matches_secret`. Default
is ``"substring"`` (legacy behaviour). Pass ``mode="word"`` for
segment-aware matching that avoids false positives like
``compass``/``tokenizer``.

OI-540: *excluded_field_patterns* lists keyword-argument names
(e.g. ``["correct_pattern", "example"]``) whose string values are
exempt from entropy scanning — documentation fields in
namedtuple/dataclass constructors.

Returns ``(passed, detail_message)``; ``passed`` is False when at least
one violation is found.

Definition at line 241 of file secret_scanner.py.

Here is the call graph for this function:

Variable Documentation

◆ _CAMEL_SPLIT_RE

secret_scanner._CAMEL_SPLIT_RE = re.compile(r"(?<=[a-z0-9])(?=[A-Z])")
protected

Definition at line 57 of file secret_scanner.py.

◆ _COMMENT_ENTROPY_DEFAULT

float secret_scanner._COMMENT_ENTROPY_DEFAULT = 5.0
protected

Definition at line 136 of file secret_scanner.py.

◆ _DOCSTRING_ENTROPY_DEFAULT

float secret_scanner._DOCSTRING_ENTROPY_DEFAULT = 5.0
protected

Definition at line 135 of file secret_scanner.py.

◆ _ENTROPY_DEFAULT_THRESHOLD

float secret_scanner._ENTROPY_DEFAULT_THRESHOLD = 4.5
protected

Definition at line 134 of file secret_scanner.py.

◆ _ENTROPY_MIN_LENGTH

int secret_scanner._ENTROPY_MIN_LENGTH = 16
protected

Definition at line 137 of file secret_scanner.py.

◆ _SECRET_NAME_PATTERNS

dict secret_scanner._SECRET_NAME_PATTERNS
protected
Initial value:
1= {
2 "password", "passwd", "pwd",
3 "secret", "secret_key", "private_key",
4 "api_key", "apikey", "api_secret",
5 "token", "auth_token", "access_token", "refresh_token",
6 "connection_string", "conn_str", "dsn", "database_url", "db_url",
7 "aws_secret", "aws_key", "aws_access",
8 "credentials", "client_secret",
9}

Definition at line 64 of file secret_scanner.py.