codejury 0.18.0__tar.gz → 0.19.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codejury-0.18.0 → codejury-0.19.0}/PKG-INFO +1 -1
- codejury-0.19.0/codejury/data/detection.yaml +17 -0
- codejury-0.19.0/codejury/data/frameworks/go/echo.md +31 -0
- codejury-0.19.0/codejury/data/frameworks/go/gin.md +34 -0
- codejury-0.19.0/codejury/data/frameworks/javascript/express.md +36 -0
- codejury-0.19.0/codejury/data/frameworks/javascript/nestjs.md +36 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/frameworks/python/django.md +1 -0
- codejury-0.19.0/codejury/data/frameworks/python/fastapi.md +37 -0
- codejury-0.19.0/codejury/data/frameworks/python/flask.md +37 -0
- codejury-0.19.0/codejury/data/languages/go.md +35 -0
- codejury-0.19.0/codejury/data/languages/javascript.md +34 -0
- codejury-0.19.0/codejury/data/languages/typescript.md +28 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/methodologies/repo-review.md +77 -18
- codejury-0.19.0/codejury/detection.py +56 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/diff/engine.py +1 -1
- {codejury-0.18.0 → codejury-0.19.0}/codejury/diff/findings_filter.py +2 -21
- {codejury-0.18.0 → codejury-0.19.0}/codejury/guides.py +27 -12
- {codejury-0.18.0 → codejury-0.19.0}/codejury/repo/model.py +23 -26
- {codejury-0.18.0 → codejury-0.19.0}/codejury/repo/scaffold.py +73 -24
- {codejury-0.18.0 → codejury-0.19.0}/codejury/resources.py +1 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury.egg-info/PKG-INFO +1 -1
- {codejury-0.18.0 → codejury-0.19.0}/codejury.egg-info/SOURCES.txt +12 -0
- {codejury-0.18.0 → codejury-0.19.0}/pyproject.toml +1 -1
- codejury-0.19.0/tests/test_detection.py +35 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_diff_engine.py +1 -1
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_guides.py +10 -4
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_repo_scaffold.py +44 -7
- {codejury-0.18.0 → codejury-0.19.0}/LICENSE +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/README.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/__init__.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/cli.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/frameworks/python/celery.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/languages/python.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/methodologies/security-review-memory.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/protocols/oauth.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/SKILL.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/business-logic.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/code-injection.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/command-injection.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/cross-site-request-forgery.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/cross-site-scripting.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/hardcoded-secrets.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/http-response-splitting.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/improper-authentication.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/information-exposure.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/insecure-cryptography.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/insecure-deserialization.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/insecure-direct-object-reference.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/insecure-transport.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/jwt-validation.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/mass-assignment.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/missing-authorization.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/open-redirect.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/path-traversal.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/race-condition.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/replay-attack.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/server-side-request-forgery.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/server-side-template-injection.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/session-fixation.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/sql-injection.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/data/vulnerabilities/xml-external-entity.md +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/diff/__init__.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/diff/debate.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/diff/debate_prompts.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/diff/prompts.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/diff/runner.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/diff/vulnerabilities.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/domain/__init__.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/domain/finding.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/json_parse.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/mddoc.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/__init__.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/anthropic.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/base.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/factory.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/litellm.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/mock.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/openai.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/openai_format.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/providers/retry.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/repo/__init__.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury/report.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/setup.cfg +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_cli_audit.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_diff_debate.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_json_parse.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_litellm_provider.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_mddoc.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_openai_format.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_openai_provider.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_repo_model.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_report.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_retry_provider.py +0 -0
- {codejury-0.18.0 → codejury-0.19.0}/tests/test_vulnerabilities.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: AI code security review: an adversarial diff-audit engine and an agent-driven whole-repo review methodology, with security knowledge as rich rules
|
|
5
5
|
Author: AISecLabs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# File and path classification, across ecosystems: what the engine treats as a
|
|
2
|
+
# source file, a dependency manifest, a noise directory, or test code. Kept here
|
|
3
|
+
# so the implementation enumerates no language itself, adding a language is a
|
|
4
|
+
# data edit. This is distinct from a guide's stack detection, which decides which
|
|
5
|
+
# language, framework, or protocol applies.
|
|
6
|
+
|
|
7
|
+
skip_dirs: [".git", ".venv", "venv", "node_modules", "__pycache__", ".mypy_cache", ".pytest_cache"]
|
|
8
|
+
|
|
9
|
+
source_extensions: [".py", ".js", ".ts", ".tsx", ".jsx", ".mjs", ".go", ".rb", ".java", ".kt", ".php", ".cs", ".scala", ".rs"]
|
|
10
|
+
|
|
11
|
+
config_extensions: [".yaml", ".yml", ".toml", ".ini", ".cfg", ".conf", ".env"]
|
|
12
|
+
|
|
13
|
+
manifests: ["requirements.txt", "requirements-dev.txt", "pyproject.toml", "setup.py", "Pipfile", "package.json", "go.mod", "Gemfile", "pom.xml", "build.gradle", "Cargo.toml", "composer.json"]
|
|
14
|
+
|
|
15
|
+
test_dirs: ["test", "tests", "__tests__", "__mocks__", "mocks", "fixtures", "testdata", "e2e", "spec", "specs"]
|
|
16
|
+
|
|
17
|
+
test_name_patterns: ["test_*", "*_test.*", "*.test.*", "*.spec.*"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: echo
|
|
3
|
+
title: Echo
|
|
4
|
+
kind: framework
|
|
5
|
+
language: go
|
|
6
|
+
detect:
|
|
7
|
+
manifest: ["labstack/echo"]
|
|
8
|
+
imports: ["github.com/labstack/echo"]
|
|
9
|
+
entrypoint_files: ["*main.go", "*/handlers/*.go", "*/handler/*.go", "*/api/*.go", "*/routes/*.go", "*/controllers/*.go"]
|
|
10
|
+
entrypoint_markers: ["echo.New(", "echo.Context", "e.GET", "e.POST", ".GET(", ".POST(", ".Group(", "c.Param", "c.QueryParam", "c.Bind"]
|
|
11
|
+
logic_layers: ["*/service/*.go", "*/services/*.go", "*/usecase/*.go", "*/repository/*.go", "*/repo/*.go", "*/store/*.go", "*/dao/*.go", "*/model/*.go"]
|
|
12
|
+
---
|
|
13
|
+
# Echo Review Notes
|
|
14
|
+
|
|
15
|
+
## Entrypoints
|
|
16
|
+
- Handlers have the signature `func(c echo.Context) error`, registered with
|
|
17
|
+
`e.GET`, `.POST`, and grouped under `e.Group`. Input comes from `c.Param`,
|
|
18
|
+
`c.QueryParam`, `c.FormValue`, `c.Request().Header`, and `c.Bind` into a struct.
|
|
19
|
+
|
|
20
|
+
## Authorization / IDOR
|
|
21
|
+
- Auth is middleware, applied globally, on a `Group`, or per route. The flaw to
|
|
22
|
+
hunt is a route registered outside the authenticated group, inheriting no
|
|
23
|
+
check. Compare grouped routes against routes on the bare instance.
|
|
24
|
+
- IDOR: a record loaded by `c.Param("id")` with no owner or tenant scope.
|
|
25
|
+
|
|
26
|
+
## Common Sinks / Gotchas
|
|
27
|
+
- SQL: `fmt.Sprintf` into `db.Query` or `db.Exec`, instead of placeholders.
|
|
28
|
+
- Command: `exec.Command` built from input.
|
|
29
|
+
- Path: `c.File` or `c.Attachment` and `filepath.Join` on a path from input.
|
|
30
|
+
- `c.Bind` into a struct with privileged fields is mass assignment.
|
|
31
|
+
- A returned `error` that the caller drops can hide a failed auth or validation.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: gin
|
|
3
|
+
title: Gin
|
|
4
|
+
kind: framework
|
|
5
|
+
language: go
|
|
6
|
+
detect:
|
|
7
|
+
manifest: ["gin-gonic/gin"]
|
|
8
|
+
imports: ["github.com/gin-gonic/gin"]
|
|
9
|
+
entrypoint_files: ["*main.go", "*/handlers/*.go", "*/handler/*.go", "*/api/*.go", "*/routes/*.go", "*/controllers/*.go"]
|
|
10
|
+
entrypoint_markers: ["gin.Default(", "gin.New(", "*gin.Context", "router.GET", "router.POST", ".GET(", ".POST(", ".Group(", "c.Param", "c.Query", "c.ShouldBind"]
|
|
11
|
+
logic_layers: ["*/service/*.go", "*/services/*.go", "*/usecase/*.go", "*/repository/*.go", "*/repo/*.go", "*/store/*.go", "*/dao/*.go", "*/model/*.go"]
|
|
12
|
+
---
|
|
13
|
+
# Gin Review Notes
|
|
14
|
+
|
|
15
|
+
## Entrypoints
|
|
16
|
+
- Handlers have the signature `func(c *gin.Context)`, registered with
|
|
17
|
+
`router.GET`, `.POST`, and grouped under `router.Group`. Input comes from
|
|
18
|
+
`c.Param`, `c.Query`, `c.PostForm`, `c.GetHeader`, and `c.ShouldBindJSON` or
|
|
19
|
+
`c.Bind` into a struct.
|
|
20
|
+
|
|
21
|
+
## Authorization / IDOR
|
|
22
|
+
- Auth is middleware, applied globally, on a `Group`, or per route. The classic
|
|
23
|
+
flaw is a route registered outside the authenticated group, so it inherits no
|
|
24
|
+
check. Compare a group's routes against the routes registered on the bare
|
|
25
|
+
engine.
|
|
26
|
+
- IDOR: a record loaded by `c.Param("id")` with no owner or tenant scope.
|
|
27
|
+
|
|
28
|
+
## Common Sinks / Gotchas
|
|
29
|
+
- SQL: `fmt.Sprintf` into `db.Query` or `db.Exec`, instead of placeholders.
|
|
30
|
+
- Command: `exec.Command` built from input.
|
|
31
|
+
- Path: `c.File` or `filepath.Join` on a path from input, the traversal sink.
|
|
32
|
+
- `c.ShouldBindJSON` into a struct with privileged fields is mass assignment.
|
|
33
|
+
- A handler that ignores the error from a bind or an auth call proceeds as if it
|
|
34
|
+
passed.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: express
|
|
3
|
+
title: Express
|
|
4
|
+
kind: framework
|
|
5
|
+
language: javascript
|
|
6
|
+
detect:
|
|
7
|
+
manifest: ["express"]
|
|
8
|
+
imports: ["require('express')", "require(\"express\")", "from 'express'"]
|
|
9
|
+
entrypoint_files: ["*app.js", "*server.js", "*app.ts", "*server.ts", "*/routes/*.js", "*/routes/*.ts", "*/controllers/*.js", "*/controllers/*.ts", "*router*.js", "*router*.ts"]
|
|
10
|
+
entrypoint_markers: ["express()", "app.get(", "app.post(", "app.use(", "router.get(", "router.post(", ".get(", ".post(", "req.params", "req.query", "req.body"]
|
|
11
|
+
logic_layers: ["*/services/*.js", "*/services/*.ts", "*/models/*.js", "*/models/*.ts", "*/repositories/*.js", "*/repositories/*.ts", "*/dao/*.js", "*/dao/*.ts"]
|
|
12
|
+
---
|
|
13
|
+
# Express Review Notes
|
|
14
|
+
|
|
15
|
+
Works the same in JavaScript and TypeScript. See the JavaScript guide for the
|
|
16
|
+
runtime sinks.
|
|
17
|
+
|
|
18
|
+
## Entrypoints
|
|
19
|
+
- Routes are `app.get` / `app.post` / `router.*`, and a `Router` mounted with
|
|
20
|
+
`app.use("/prefix", router)`. The handler is `(req, res, next)`. Input is
|
|
21
|
+
`req.params`, `req.query`, `req.body`, `req.headers`, and `req.cookies`.
|
|
22
|
+
|
|
23
|
+
## Authorization / IDOR
|
|
24
|
+
- Auth is middleware, passed to `app.use` or per route. The flaw to hunt is a
|
|
25
|
+
route mounted before the auth middleware, or one that omits the middleware its
|
|
26
|
+
siblings have, so order and placement matter.
|
|
27
|
+
- IDOR: a record loaded by `req.params.id` with no owner or tenant scope.
|
|
28
|
+
|
|
29
|
+
## Common Sinks / Gotchas
|
|
30
|
+
- SQL and NoSQL: a query built by string concatenation, or a Mongo filter built
|
|
31
|
+
straight from `req.body`, the operator-injection sink.
|
|
32
|
+
- Command: `child_process.exec` from input. Code: `eval` from input.
|
|
33
|
+
- Path: `res.sendFile` or `path.join` on a path from input, the traversal sink.
|
|
34
|
+
- Open redirect via `res.redirect(req.query...)`, and prototype pollution from a
|
|
35
|
+
body merge.
|
|
36
|
+
- A body spread whole into a model or an ORM create is mass assignment.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: nestjs
|
|
3
|
+
title: NestJS
|
|
4
|
+
kind: framework
|
|
5
|
+
language: javascript
|
|
6
|
+
detect:
|
|
7
|
+
manifest: ["@nestjs/core", "@nestjs/common"]
|
|
8
|
+
imports: ["@nestjs/common", "@nestjs/core"]
|
|
9
|
+
entrypoint_files: ["*.controller.ts", "*.controller.js", "*/controllers/*.ts", "*.resolver.ts", "*.gateway.ts"]
|
|
10
|
+
entrypoint_markers: ["@Controller(", "@Get(", "@Post(", "@Put(", "@Delete(", "@Body(", "@Param(", "@Query(", "@UseGuards("]
|
|
11
|
+
logic_layers: ["*.service.ts", "*.service.js", "*/services/*.ts", "*.repository.ts", "*/repositories/*.ts", "*.entity.ts", "*/dao/*.ts"]
|
|
12
|
+
---
|
|
13
|
+
# NestJS Review Notes
|
|
14
|
+
|
|
15
|
+
Usually TypeScript on Node. See the JavaScript and TypeScript guides for the
|
|
16
|
+
runtime sinks and for why types do not sanitize input.
|
|
17
|
+
|
|
18
|
+
## Entrypoints
|
|
19
|
+
- A `@Controller` class with `@Get` / `@Post` methods. Input binds through
|
|
20
|
+
`@Param`, `@Query`, `@Body`, and `@Headers`. GraphQL resolvers and WebSocket
|
|
21
|
+
gateways are entrypoints too.
|
|
22
|
+
|
|
23
|
+
## Authorization / IDOR
|
|
24
|
+
- Access control is a guard applied with `@UseGuards`, at the controller or the
|
|
25
|
+
method, plus role decorators. The flaw to hunt is a route or controller missing
|
|
26
|
+
the guard its siblings declare, and a guard that authenticates but does not
|
|
27
|
+
authorize the specific resource.
|
|
28
|
+
- IDOR: a handler that loads by `@Param("id")` with no owner or tenant check.
|
|
29
|
+
|
|
30
|
+
## Common Sinks / Gotchas
|
|
31
|
+
- Mass assignment: a `@Body` DTO with no `ValidationPipe` and `whitelist: true`
|
|
32
|
+
binds any field the client sends. Confirm the pipe is global or applied.
|
|
33
|
+
- SQL: a TypeORM or Prisma raw query built from input.
|
|
34
|
+
- SSRF: a server-side `fetch` or `axios` to a URL from input.
|
|
35
|
+
- A custom guard that returns true on an unhandled path, or reads the user from a
|
|
36
|
+
header the client controls.
|
|
@@ -9,6 +9,7 @@ detect:
|
|
|
9
9
|
imports: ["from django", "import django"]
|
|
10
10
|
entrypoint_files: ["*urls.py", "*views.py", "*viewsets.py", "*/views/*.py", "*serializers.py", "*api.py", "*consumers.py"]
|
|
11
11
|
entrypoint_markers: ["APIView", "ViewSet", "@api_view", "@action", "router.register", "path(", "re_path(", "as_view("]
|
|
12
|
+
logic_layers: ["*/controllers/*.py", "*controllers.py", "*/managers/*.py", "*managers.py", "*/dao/*.py", "*dao.py", "*/services/*.py", "*services.py", "*/models/*.py", "*models.py"]
|
|
12
13
|
---
|
|
13
14
|
# Django Review Notes
|
|
14
15
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: fastapi
|
|
3
|
+
title: FastAPI
|
|
4
|
+
kind: framework
|
|
5
|
+
language: python
|
|
6
|
+
detect:
|
|
7
|
+
manifest: ["fastapi"]
|
|
8
|
+
imports: ["from fastapi", "import fastapi"]
|
|
9
|
+
entrypoint_files: ["*main.py", "*/routers/*.py", "*/api/*.py", "*api.py", "*routes.py", "*/endpoints/*.py"]
|
|
10
|
+
entrypoint_markers: ["FastAPI(", "APIRouter(", "@app.get", "@app.post", "@router.get", "@router.post", "Depends("]
|
|
11
|
+
logic_layers: ["*/services/*.py", "*services.py", "*/models/*.py", "*models.py", "*/repositories/*.py", "*/crud/*.py", "*/dao/*.py"]
|
|
12
|
+
---
|
|
13
|
+
# FastAPI Review Notes
|
|
14
|
+
|
|
15
|
+
## Entrypoints
|
|
16
|
+
- Path operations decorated with `@app.get` / `@app.post` or `@router.*` on an
|
|
17
|
+
`APIRouter`. Inputs arrive as path and query parameters, and as a request body
|
|
18
|
+
validated by a Pydantic model.
|
|
19
|
+
- A Pydantic model bounds the body's shape, but an over-wide model still binds
|
|
20
|
+
privileged fields, the mass-assignment shape.
|
|
21
|
+
|
|
22
|
+
## Authorization / IDOR
|
|
23
|
+
- Auth and access control run through `Depends`, for example a dependency that
|
|
24
|
+
resolves the current user or checks a scope. Note an endpoint that omits the
|
|
25
|
+
dependency its siblings use, or a dependency that authenticates but does not
|
|
26
|
+
authorize the specific resource.
|
|
27
|
+
- IDOR: an endpoint that loads a record by an id parameter with no owner or tenant
|
|
28
|
+
check.
|
|
29
|
+
|
|
30
|
+
## Common Sinks / Gotchas
|
|
31
|
+
- SQL: a raw query or an ORM `text()` built from a parameter.
|
|
32
|
+
- SSRF: `httpx` or `requests` to a URL from input, common in webhook and fetch
|
|
33
|
+
endpoints.
|
|
34
|
+
- Path: `FileResponse` on a path from input.
|
|
35
|
+
- `CORSMiddleware` with `allow_origins=["*"]` together with credentials, and an
|
|
36
|
+
OAuth2 bearer dependency that decodes a token without verifying signature,
|
|
37
|
+
audience, and expiry.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: flask
|
|
3
|
+
title: Flask
|
|
4
|
+
kind: framework
|
|
5
|
+
language: python
|
|
6
|
+
detect:
|
|
7
|
+
manifest: ["flask"]
|
|
8
|
+
imports: ["from flask", "import flask"]
|
|
9
|
+
entrypoint_files: ["*app.py", "*views.py", "*routes.py", "*/views/*.py", "*/blueprints/*.py", "*api.py"]
|
|
10
|
+
entrypoint_markers: ["@app.route", ".route(", "Blueprint(", "add_url_rule(", "MethodView", "@app.before_request"]
|
|
11
|
+
logic_layers: ["*/services/*.py", "*services.py", "*/models/*.py", "*models.py", "*/repositories/*.py", "*/dao/*.py"]
|
|
12
|
+
---
|
|
13
|
+
# Flask Review Notes
|
|
14
|
+
|
|
15
|
+
## Entrypoints
|
|
16
|
+
- Routes are functions decorated with `@app.route` or `@bp.route`, or registered
|
|
17
|
+
with `add_url_rule`. Blueprints mount a group under a URL prefix. Class views
|
|
18
|
+
subclass `MethodView`.
|
|
19
|
+
- Read input from `request.args`, `request.form`, `request.values`,
|
|
20
|
+
`request.json`, `request.files`, `request.headers`, and `request.cookies`, all
|
|
21
|
+
attacker-controlled.
|
|
22
|
+
|
|
23
|
+
## Authorization / IDOR
|
|
24
|
+
- Auth is enforced by a `@login_required` style decorator, a `before_request`
|
|
25
|
+
hook, or an explicit check in the view. Note where it is and where a route
|
|
26
|
+
lacks it.
|
|
27
|
+
- IDOR: a model fetched by an id from the request with no owner or tenant scope,
|
|
28
|
+
then returned.
|
|
29
|
+
|
|
30
|
+
## Common Sinks / Gotchas
|
|
31
|
+
- SSTI: `render_template_string` on input, or `Markup` and `|safe` on unescaped
|
|
32
|
+
input.
|
|
33
|
+
- SQL: raw `cursor.execute` or an ORM `text()` built from input.
|
|
34
|
+
- Path: `send_file` or `send_from_directory` with a path from input, the traversal
|
|
35
|
+
sink.
|
|
36
|
+
- A hardcoded `SECRET_KEY`, `debug=True` in production, and an open redirect via
|
|
37
|
+
`redirect(request.args[...])`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: go
|
|
3
|
+
title: Go
|
|
4
|
+
kind: language
|
|
5
|
+
detect:
|
|
6
|
+
files: ["*.go", "go.mod"]
|
|
7
|
+
entrypoint_files: ["*main.go", "*/handlers/*.go", "*/handler/*.go", "*/api/*.go", "*/routes/*.go"]
|
|
8
|
+
entrypoint_markers: ["http.HandleFunc", "http.ListenAndServe", "ServeMux", "http.Handler", "func(w http.ResponseWriter"]
|
|
9
|
+
logic_layers: ["*/service/*.go", "*/services/*.go", "*/usecase/*.go", "*/repository/*.go", "*/repo/*.go", "*/store/*.go", "*/dao/*.go", "*/model/*.go", "*/models/*.go"]
|
|
10
|
+
---
|
|
11
|
+
# Go Review Notes
|
|
12
|
+
|
|
13
|
+
Where untrusted input enters beyond web routes, which the framework guides cover.
|
|
14
|
+
The standard `net/http` server is itself an entrypoint: a handler that takes an
|
|
15
|
+
`http.ResponseWriter` and an `*http.Request`, registered with `http.HandleFunc`
|
|
16
|
+
or a `ServeMux`. Read the request through `r.URL.Query`, `r.FormValue`, `r.PathValue`,
|
|
17
|
+
`r.Header`, and the decoded body, all attacker-controlled.
|
|
18
|
+
|
|
19
|
+
## Common Sinks
|
|
20
|
+
- SQL: a query built with `fmt.Sprintf` or string concatenation passed to
|
|
21
|
+
`db.Query` or `db.Exec`. Use placeholders, never build SQL from input.
|
|
22
|
+
- Command: `exec.Command` with a shell or with arguments built from input,
|
|
23
|
+
`os/exec` reaching `sh -c`.
|
|
24
|
+
- Path: `filepath.Join` or `os.Open` on a path from input with no `filepath.Clean`
|
|
25
|
+
and containment check, the traversal sink.
|
|
26
|
+
- SSRF: `http.Get`, `http.NewRequest`, or a client `Do` on a URL from input.
|
|
27
|
+
- Deserialization and templates: `encoding/gob`, `text/template` rendering input,
|
|
28
|
+
and `html/template` used with the wrong escaping context.
|
|
29
|
+
|
|
30
|
+
## Gotchas
|
|
31
|
+
- Errors ignored with `_` can skip a security check whose failure is never seen.
|
|
32
|
+
- A type assertion or `interface{}` body decoded with `json.Unmarshal` into a
|
|
33
|
+
wide struct is mass assignment if privileged fields are bound.
|
|
34
|
+
- Goroutines and shared state without a lock are a race, relevant to one-time
|
|
35
|
+
tokens and balances.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: javascript
|
|
3
|
+
title: JavaScript
|
|
4
|
+
kind: language
|
|
5
|
+
detect:
|
|
6
|
+
files: ["*.js", "*.mjs", "*.cjs", "*.jsx"]
|
|
7
|
+
entrypoint_files: ["*server.js", "*app.js", "*index.js", "*/routes/*.js", "*/handlers/*.js", "*/api/*.js"]
|
|
8
|
+
entrypoint_markers: ["http.createServer", "createServer(", "require('http')", "addEventListener('fetch'", "exports.handler"]
|
|
9
|
+
logic_layers: ["*/services/*.js", "*/service/*.js", "*/models/*.js", "*/repositories/*.js", "*/dao/*.js", "*service*.js", "*model*.js"]
|
|
10
|
+
---
|
|
11
|
+
# JavaScript Review Notes
|
|
12
|
+
|
|
13
|
+
Node is the usual runtime. Where untrusted input enters beyond web routes, which
|
|
14
|
+
the framework guides cover. A plain `http.createServer` callback, a serverless
|
|
15
|
+
`exports.handler`, and a `fetch` event listener are entrypoints too. Read the
|
|
16
|
+
request body, query, params, headers, and cookies as attacker-controlled.
|
|
17
|
+
|
|
18
|
+
## Common Sinks
|
|
19
|
+
- Command: `child_process.exec`, `execSync`, or `spawn` with a shell, built from
|
|
20
|
+
input.
|
|
21
|
+
- Code: `eval`, `new Function`, `vm.runInContext` on input.
|
|
22
|
+
- SQL and NoSQL: a query built by string concatenation or template literal, and a
|
|
23
|
+
Mongo query that takes a raw object from the body, the operator-injection sink.
|
|
24
|
+
- Path: `fs.readFile`, `path.join`, or `res.sendFile` on a path from input with no
|
|
25
|
+
containment check.
|
|
26
|
+
- SSRF: `fetch`, `axios`, or `http.request` on a URL from input.
|
|
27
|
+
- Prototype pollution: a recursive merge or `lodash.merge` of a request body into
|
|
28
|
+
an object, reaching `__proto__`.
|
|
29
|
+
|
|
30
|
+
## Gotchas
|
|
31
|
+
- A missing `await` on an async auth check lets the handler proceed before it
|
|
32
|
+
resolves.
|
|
33
|
+
- A regex from input, or a catastrophic backtracking regex on input, is a ReDoS.
|
|
34
|
+
- `JSON.parse` of input into a wide model and assigning it whole is mass assignment.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: typescript
|
|
3
|
+
title: TypeScript
|
|
4
|
+
kind: language
|
|
5
|
+
detect:
|
|
6
|
+
files: ["*.ts", "*.tsx"]
|
|
7
|
+
entrypoint_files: ["*server.ts", "*app.ts", "*index.ts", "*/routes/*.ts", "*/handlers/*.ts", "*/api/*.ts"]
|
|
8
|
+
entrypoint_markers: ["http.createServer", "createServer(", "addEventListener('fetch'", "export const handler", "export async function handler"]
|
|
9
|
+
logic_layers: ["*/services/*.ts", "*/service/*.ts", "*/models/*.ts", "*/repositories/*.ts", "*/dao/*.ts", "*service*.ts", "*model*.ts"]
|
|
10
|
+
---
|
|
11
|
+
# TypeScript Review Notes
|
|
12
|
+
|
|
13
|
+
TypeScript runs as JavaScript on Node, so the JavaScript sinks and gotchas all
|
|
14
|
+
apply, read the JavaScript guide. The Node frameworks are shared, so an Express
|
|
15
|
+
or Nest service in TypeScript uses the same framework guides under
|
|
16
|
+
`frameworks/javascript`.
|
|
17
|
+
|
|
18
|
+
## What Types Do Not Protect
|
|
19
|
+
- Types are erased at runtime. A value typed as `string` is still attacker
|
|
20
|
+
input, so type annotations do not sanitize a query, a path, or a command.
|
|
21
|
+
- A DTO typed in the code does not constrain the request body unless a runtime
|
|
22
|
+
validator such as class-validator or zod actually enforces it, so an
|
|
23
|
+
unvalidated body is still mass assignment.
|
|
24
|
+
- An `as` cast or `any` hides an untrusted value behind a safe-looking type.
|
|
25
|
+
- `JSON.parse` returns `any`, so a parsed body carries no real guarantees.
|
|
26
|
+
|
|
27
|
+
Beyond that, hunt the same sinks as JavaScript: command, code, SQL and NoSQL,
|
|
28
|
+
path traversal, SSRF, and prototype pollution.
|
|
@@ -29,6 +29,9 @@ Workspace: `<workspace>/<project>/`, created for you, holding `entrypoints/`,
|
|
|
29
29
|
4. Read the relevant vulnerability files under the shipped `vulnerabilities/` for
|
|
30
30
|
the target's stack such as sql-injection, idor, ssrf, jwt-validation, or
|
|
31
31
|
insecure-deserialization.
|
|
32
|
+
5. Read `analysis/_trace_targets.md`, the seeded downstream logic-layer files such
|
|
33
|
+
as managers and dao to trace into, and `analysis/_rounds.md`, the round ledger
|
|
34
|
+
you must keep. See "Trace Attack Paths" and "Completeness Gate".
|
|
32
35
|
|
|
33
36
|
## Map the Attack Surface
|
|
34
37
|
|
|
@@ -94,8 +97,11 @@ Then hunt three shapes:
|
|
|
94
97
|
A whole-repo review earns its keep by reasoning *across files*: a flaw is usually
|
|
95
98
|
a source in one file reaching a dangerous sink in another, past a control defined
|
|
96
99
|
in a third, for example a route that trusts a helper which skips signature checks, or an id that
|
|
97
|
-
reaches a query with no ownership check.
|
|
98
|
-
|
|
100
|
+
reaches a query with no ownership check. The flaw usually lives below the
|
|
101
|
+
entrypoint, in a manager, a controller, a dao, or a service, not in the view. The
|
|
102
|
+
seeded `analysis/_trace_targets.md` lists those downstream logic-layer files. For
|
|
103
|
+
each promising source, follow the calls out of the entrypoint into those layers
|
|
104
|
+
to the real sink, and record the path in `analysis/`:
|
|
99
105
|
|
|
100
106
|
- **Source**: the entrypoint and the attacker-controlled value.
|
|
101
107
|
- **Sink**: the dangerous operation it reaches such as a query, shell, file path, fetch,
|
|
@@ -106,19 +112,45 @@ path and record it in `analysis/`:
|
|
|
106
112
|
|
|
107
113
|
The vulnerability is a path with a reachable sink and no adequate control. Record
|
|
108
114
|
the system's trust boundaries and auth/authz model in `analysis/` once, so every
|
|
109
|
-
trace can refer to it instead of restating it.
|
|
115
|
+
trace can refer to it instead of restating it. An entrypoint is not done until its
|
|
116
|
+
path is traced through the downstream layers to a sink or cleared, since stopping
|
|
117
|
+
at the view is what hides the deep flaw, for example the missing lock in a dao or
|
|
118
|
+
the skipped expiry check in a manager.
|
|
119
|
+
|
|
120
|
+
## Controls That Live in a Library
|
|
121
|
+
|
|
122
|
+
An entrypoint's security control is often not in the first-party code at all: the
|
|
123
|
+
authentication, the signature or replay check, the permission test is implemented
|
|
124
|
+
in a library the endpoint calls. You cannot judge whether the endpoint is
|
|
125
|
+
exploitable from the first-party code alone, because the control that would stop
|
|
126
|
+
the attack lives in the library.
|
|
127
|
+
|
|
128
|
+
So when a traced path relies on a control a library provides, follow into that
|
|
129
|
+
library's relevant function and verify it actually enforces the control, for
|
|
130
|
+
example that a signature check also binds a nonce or a timestamp window, or that
|
|
131
|
+
an auth helper truly validates the caller. Read the specific function the path
|
|
132
|
+
depends on, not the whole library, and read it where it is installed or vendored.
|
|
133
|
+
|
|
134
|
+
This is about this app's exposure, so it applies to any library, internal or
|
|
135
|
+
third-party. It is not auditing the library for its own bugs, which belongs to
|
|
136
|
+
that library's own review. It is confirming that the control your endpoint relies
|
|
137
|
+
on holds here.
|
|
110
138
|
|
|
111
139
|
## Scope
|
|
112
140
|
|
|
113
141
|
Report only HIGH / CRITICAL, exploitable, high-confidence issues. **Do not report**
|
|
114
142
|
regardless of severity, dependency CVEs, style or best-practice notes,
|
|
115
143
|
speculative issues you cannot tie to a concrete exploit, and risks that only
|
|
116
|
-
matter if production config is leaked.
|
|
144
|
+
matter if production config is leaked. A control that a library fails to enforce
|
|
145
|
+
for a reachable first-party entrypoint is not a dependency CVE, it is this app's
|
|
146
|
+
exploitable exposure, so it is in scope. See "Controls That Live in a Library".
|
|
117
147
|
|
|
118
148
|
## Recording an Issue
|
|
119
149
|
|
|
120
|
-
Write one `issues/<name>.md` per
|
|
121
|
-
|
|
150
|
+
Write one `issues/<name>.md` per issue, and save its PoC as a real runnable file
|
|
151
|
+
`issues/poc_<name>.<ext>`, a script or an `.http` file, not a sketch in prose. If
|
|
152
|
+
you cannot write a concrete runnable PoC, the finding is most likely a false
|
|
153
|
+
positive, so do not report it. Each issue file must have:
|
|
122
154
|
|
|
123
155
|
```markdown
|
|
124
156
|
# <title>
|
|
@@ -126,6 +158,7 @@ confirm with high confidence. Each must have:
|
|
|
126
158
|
- Risk: HIGH | CRITICAL
|
|
127
159
|
- Type: IDOR | auth bypass | signature flaw | business logic | ...
|
|
128
160
|
- Source: `<METHOD> <path>` or the non-HTTP entrypoint (queue, deserializer, ...)
|
|
161
|
+
- Verification: reproduced | blocked, needs <what> | not run
|
|
129
162
|
|
|
130
163
|
## Analysis
|
|
131
164
|
(cite exact file paths and line numbers)
|
|
@@ -134,22 +167,29 @@ confirm with high confidence. Each must have:
|
|
|
134
167
|
(end-to-end, actionable steps)
|
|
135
168
|
|
|
136
169
|
## PoC
|
|
137
|
-
(
|
|
170
|
+
(the path to `issues/poc_<name>.<ext>` and how to run it)
|
|
138
171
|
|
|
139
172
|
## Verification
|
|
140
|
-
(
|
|
173
|
+
(the actual output of running the PoC, or the exact blocker)
|
|
141
174
|
|
|
142
175
|
## Fix
|
|
143
176
|
```
|
|
144
177
|
|
|
145
|
-
## PoC Verification,
|
|
178
|
+
## PoC Verification, the False-Positive Gate
|
|
146
179
|
|
|
147
|
-
|
|
148
|
-
|
|
180
|
+
A finding is a hypothesis until a PoC proves it. The tool, and you, can be
|
|
181
|
+
confident and still wrong, so the PoC is what separates a real vulnerability from
|
|
182
|
+
a plausible misread. Confirm each issue by running its PoC against a sandbox or
|
|
183
|
+
dev environment, and gate reporting on the result:
|
|
149
184
|
|
|
150
|
-
-
|
|
151
|
-
|
|
152
|
-
-
|
|
185
|
+
- **Reproduced**: the PoC ran and triggered the issue. Only these are reported as
|
|
186
|
+
confirmed HIGH / CRITICAL.
|
|
187
|
+
- **Blocked**: the PoC is written and correct but you need something only the
|
|
188
|
+
operator has, so stop and ask, for an auth cookie or token, an MFA step, or
|
|
189
|
+
specific test data or an account. Report these separately as suspected and
|
|
190
|
+
needing verification, with the exact blocker, never mixed into the confirmed
|
|
191
|
+
set.
|
|
192
|
+
- **Not run with no concrete PoC**: do not report. It is a guess.
|
|
153
193
|
|
|
154
194
|
Never run a PoC against production, and never use real credentials or perform a
|
|
155
195
|
destructive action without the operator's explicit go-ahead.
|
|
@@ -158,13 +198,32 @@ destructive action without the operator's explicit go-ahead.
|
|
|
158
198
|
|
|
159
199
|
Each round, read the workspace history first and do not repeat finished work.
|
|
160
200
|
Process leftover TODOs, otherwise pick an unreviewed ❌ or to-deepen ⚠️ source
|
|
161
|
-
from the inventory
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
201
|
+
from the inventory, trace it through the downstream layers, and log the round in
|
|
202
|
+
`analysis/_rounds.md`. One round rarely finds the deep cross-file and stateful
|
|
203
|
+
bugs. The hard classes such as authorization, replay, and broken business state
|
|
204
|
+
usually appear only after several rounds, so keep going.
|
|
205
|
+
|
|
206
|
+
## Completeness Gate
|
|
207
|
+
|
|
208
|
+
Do not report the review complete until all of the following hold. A short run
|
|
209
|
+
with most entrypoints still ❌ is an incomplete review, not a clean one, and
|
|
210
|
+
reporting it as clean is the failure this gate exists to prevent.
|
|
211
|
+
|
|
212
|
+
- Every entrypoint in the inventory is resolved to ✅, none left ❌.
|
|
213
|
+
- Each entrypoint's path is traced through the downstream layers in
|
|
214
|
+
`analysis/_trace_targets.md` to a real sink or explicitly cleared, not stopped
|
|
215
|
+
at the view.
|
|
216
|
+
- The Authorization Model pass ran: the access gate is mapped, sibling endpoints
|
|
217
|
+
compared, and IDOR and unauthenticated privileged paths checked.
|
|
218
|
+
- `analysis/_rounds.md` shows two consecutive rounds that added no new source, no
|
|
219
|
+
new traced path, and no new issue.
|
|
220
|
+
|
|
221
|
+
If any item fails, run another round. State which items pass when you report.
|
|
165
222
|
|
|
166
223
|
## On Finish
|
|
167
224
|
|
|
225
|
+
Report the confirmed findings, the ones with a reproduced PoC, separately from
|
|
226
|
+
the suspected ones still blocked on verification, so the two are never conflated.
|
|
168
227
|
Append a row to the audit history in `security-review-memory.md`, and ask the
|
|
169
228
|
operator which findings were false positives. Record those under "Confirmed false
|
|
170
229
|
positives" so future rounds skip them.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""File and path classification config, loaded from `data/detection.yaml`.
|
|
2
|
+
|
|
3
|
+
What the engine treats as a source file, a dependency manifest, a noise
|
|
4
|
+
directory, or test code, across ecosystems. Kept in data so the implementation
|
|
5
|
+
enumerates no language itself: adding a language is a data edit, not a code
|
|
6
|
+
change. This is distinct from a guide's stack detection in `guides.py`, which
|
|
7
|
+
decides which language, framework, or protocol applies.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import fnmatch
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from functools import lru_cache
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
from codejury.resources import DETECTION_FILE
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class Detection:
|
|
23
|
+
skip_dirs: frozenset[str] # directory names that are noise, never walked
|
|
24
|
+
source_extensions: frozenset[str] # extensions whose content is scanned for markers
|
|
25
|
+
config_extensions: frozenset[str] # config extensions also sampled when detecting the stack
|
|
26
|
+
manifests: tuple[str, ...] # dependency-manifest filenames read to detect the stack
|
|
27
|
+
test_dirs: frozenset[str] # directory segments that mark test code
|
|
28
|
+
test_name_patterns: tuple[str, ...] # filename globs that mark a test file
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def detection_extensions(self) -> frozenset[str]:
|
|
32
|
+
"""Source plus config, the files sampled when detecting the stack."""
|
|
33
|
+
return self.source_extensions | self.config_extensions
|
|
34
|
+
|
|
35
|
+
def is_test_path(self, path: str) -> bool:
|
|
36
|
+
"""True when a path is test code, by a test directory segment or a
|
|
37
|
+
test-file naming convention. Conservative, so a production file is not
|
|
38
|
+
suppressed."""
|
|
39
|
+
parts = path.replace("\\", "/").split("/")
|
|
40
|
+
if any(p in self.test_dirs for p in parts[:-1]):
|
|
41
|
+
return True
|
|
42
|
+
name = parts[-1].lower()
|
|
43
|
+
return any(fnmatch.fnmatch(name, pat) for pat in self.test_name_patterns)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@lru_cache(maxsize=1)
|
|
47
|
+
def load_detection() -> Detection:
|
|
48
|
+
data = yaml.safe_load(DETECTION_FILE.read_text(encoding="utf-8")) or {}
|
|
49
|
+
return Detection(
|
|
50
|
+
skip_dirs=frozenset(data.get("skip_dirs", [])),
|
|
51
|
+
source_extensions=frozenset(data.get("source_extensions", [])),
|
|
52
|
+
config_extensions=frozenset(data.get("config_extensions", [])),
|
|
53
|
+
manifests=tuple(data.get("manifests", [])),
|
|
54
|
+
test_dirs=frozenset(data.get("test_dirs", [])),
|
|
55
|
+
test_name_patterns=tuple(data.get("test_name_patterns", [])),
|
|
56
|
+
)
|
|
@@ -25,7 +25,7 @@ def guides_for_diff(diff: str) -> str:
|
|
|
25
25
|
Lives here, not in the shared guides module, because parsing a diff is a
|
|
26
26
|
diff-path concern."""
|
|
27
27
|
paths = _DIFF_PATH.findall(diff)
|
|
28
|
-
return "\n\n---\n\n".join(g.body for g in select_guides(paths,
|
|
28
|
+
return "\n\n---\n\n".join(g.body for g in select_guides(paths, source_text=diff))
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class AuditError(RuntimeError):
|
|
@@ -13,26 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
from dataclasses import dataclass, field
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
_TEST_DIRS = frozenset({
|
|
18
|
-
"test", "tests", "__tests__", "__mocks__", "mocks", "fixtures", "testdata", "e2e", "spec", "specs",
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _is_test_path(path: str) -> bool:
|
|
23
|
-
parts = path.replace("\\", "/").split("/")
|
|
24
|
-
if any(p in _TEST_DIRS for p in parts[:-1]): # a directory segment, not the filename
|
|
25
|
-
return True
|
|
26
|
-
name = parts[-1].lower()
|
|
27
|
-
if name == "conftest.py":
|
|
28
|
-
return True
|
|
29
|
-
stem = name.rsplit(".", 1)[0]
|
|
30
|
-
return (
|
|
31
|
-
stem.startswith("test_")
|
|
32
|
-
or stem.endswith("_test")
|
|
33
|
-
or stem.endswith(".test")
|
|
34
|
-
or stem.endswith(".spec")
|
|
35
|
-
)
|
|
16
|
+
from codejury.detection import load_detection
|
|
36
17
|
|
|
37
18
|
|
|
38
19
|
@dataclass(frozen=True, kw_only=True)
|
|
@@ -53,7 +34,7 @@ class FindingsFilter:
|
|
|
53
34
|
if f.confidence < self.min_confidence:
|
|
54
35
|
return f"confidence {f.confidence:.2f} below floor {self.min_confidence:.2f}"
|
|
55
36
|
path = f.file or ""
|
|
56
|
-
if self.drop_test_paths and
|
|
37
|
+
if self.drop_test_paths and load_detection().is_test_path(path):
|
|
57
38
|
return "test path (test/mock/fixture directory or test-file naming)"
|
|
58
39
|
match = next((e for e in self.exclude_paths if e and e in path), None)
|
|
59
40
|
if match:
|