Guidelines for AI coding agents working on the pydepgate codebase.
- Read CLAUDE.md for project overview, build commands, and critical constraints.
- Read CONTRIBUTING.md for the full contributor workflow and load-bearing constraint explanations.
- Understand which pipeline layer your change belongs in (parsers, analyzers, enrichers, rules, reporters, CLI). Putting work in the wrong layer is the most common mistake.
These are non-negotiable. Violating any of them creates security vulnerabilities or silent correctness bugs.
The static analyzer never executes user input. The safe partial evaluator in src/pydepgate/analyzers/_resolver.py reimplements operations from scratch. When extending the resolver:
- Model the operation. Do not delegate to the runtime.
- Never call
.replace(),.split(),getattr(), or any method on resolved values. - Write a function that takes the resolved string and literal arguments and produces the result by hand.
If you are uncertain whether a change preserves this property, stop and flag it.
Everything that passes through _scan_one_file must survive a pickle round-trip: FileScanInput, FileScanOutput, every analyzer, enricher, and rule instance. After adding any new component:
- Run
python -m unittest tests/engines/test_deploy_the_pickle.py -v - If something won't pickle (compiled regex, open file handle, thread-local), construct it at use time inside the logic, not as stored state.
The project has zero third-party runtime dependencies by design. Do not add any. If your approach requires a dependency, redesign the approach.
- Subclass the visitor base in
analyzers/_visitor.py - Emit signals with a stable, new signal_id in an unused number range under a new prefix
- Populate
signal.contextwith information rules will match on - Add
enrichment_hintsif the signal benefits from peek processing - Tests: happy-path + 3 evasion variants + 3 benign-pattern fixtures
- Picklability test must pass
- Update README "What pydepgate detects" section
- Rule goes in
src/pydepgate/rules/defaults.py - Explanation goes in
src/pydepgate/rules/explanations.py - Tests: matching case + at least one non-matching case
Global flags must use the dual-registration pattern:
- Add inside
_add_global_flagsincli/main.py, not in the top-level parser body - Use
argparse.SUPPRESSas default whenis_subparseris True - Use the real default when
is_subparseris False - See
cli/command_handlers/peek_args.pyfor prior art
- If it should appear in human output: update the allowlist in the human reporter's
_render_finding - Underscore prefix (
_key) = suppressed from JSON output (pipeline-internal only) - No underscore = included in JSON output
Any change to the shape of JSON output (new keys, renames, type changes, removals) requires bumping schema_version. Additive changes are minor bumps; breaking changes need a deprecation cycle.
If touching traffic_control/triage.py, you must:
- Document whether coverage is expanding or shrinking and for which file kinds
- Add tests asserting both the positive (files that should be analyzed are) and negative (files that should be skipped are)
Run the full suite before considering any change complete:
python -m unittest discover tests -vTest categories to contribute to when relevant:
- Happy-path: the pattern fires
- Evasion battery: obfuscated variants still fire
- False-positive battery: benign patterns don't fire
- Robustness: adversarial inputs don't crash the analyzer
- Integration: synthetic wheels/sdists through the engine
- CLI: subprocess invocation with output assertions
- Picklability: round-trip survival
A happy-path test alone is insufficient. An analyzer that catches the obvious case but fires on every benign use of similar syntax is worse than no analyzer.
- Sign off all commits (
git commit -s) under the Developer Certificate of Origin - Commits without sign-off cannot be merged
- Do not include working malware in test fixtures. Use benign structural shapes only.
- Wrong pipeline layer. Parsers don't make security decisions. Analyzers don't assign severity. Enrichers don't emit new finding types. Rules map signals to severity. Check the pipeline diagram in CLAUDE.md.
- Calling runtime methods on resolved values. The resolver models operations; it does not delegate.
value.replace(...)is wrong; a hand-written replacement function is correct. - Storing unpicklable state. Compiled regexes, file handles, and thread-locals on analyzer/enricher/rule instances break the parallelism contract. Construct at use time.
- Breaking signal ID stability. Existing IDs are stable forever. New signals get new IDs. Never rename for consistency.
- Missing the dual-registration pattern for CLI flags. Global flags that only work in one position produce confusing user-facing behavior.
- Adding context keys without updating reporters. Human reporter won't show keys not in its allowlist. Underscore prefix suppresses keys from JSON output.
- Changing triage coverage without tests. Expanding risks false positives; shrinking silently drops detection. Both need explicit test coverage.
- Tests that pass when code is right but don't fail when code is wrong. Verify your tests actually exercise the code path they claim to test.