openqodex 0.1.0

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.
Files changed (90) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +35 -0
  3. package/README.md +160 -0
  4. package/demo/baseline/.github/workflows/ci.yml +18 -0
  5. package/demo/baseline/Dockerfile +12 -0
  6. package/demo/baseline/app/__init__.py +0 -0
  7. package/demo/baseline/app/server.py +37 -0
  8. package/demo/baseline/package-lock.json +21 -0
  9. package/demo/baseline/package.json +9 -0
  10. package/demo/baseline/requirements.txt +1 -0
  11. package/demo/baseline/scripts/deploy.sh +13 -0
  12. package/demo/expected.json +126 -0
  13. package/demo/planted/.github/workflows/ci.yml +20 -0
  14. package/demo/planted/Dockerfile +11 -0
  15. package/demo/planted/app/config.py +3 -0
  16. package/demo/planted/app/search.py +17 -0
  17. package/demo/planted/app/server.py +40 -0
  18. package/demo/planted/package-lock.json +21 -0
  19. package/demo/planted/package.json +9 -0
  20. package/demo/planted/scripts/deploy.sh +13 -0
  21. package/dist/bin.js +42546 -0
  22. package/docs/agents.md +121 -0
  23. package/docs/cli.md +161 -0
  24. package/docs/config.md +165 -0
  25. package/docs/custom-scanners.md +164 -0
  26. package/docs/faq.md +54 -0
  27. package/docs/github-action.md +73 -0
  28. package/docs/index.md +29 -0
  29. package/docs/llms.txt +13 -0
  30. package/docs/quickstart.md +82 -0
  31. package/docs/scanners.md +148 -0
  32. package/docs/security.md +74 -0
  33. package/docs/telemetry.md +9 -0
  34. package/lenses/a11y-icon-only-button-no-aria-label.md +51 -0
  35. package/lenses/array-iteration-missing-key-prop.md +31 -0
  36. package/lenses/async-await-in-loop-n-plus-one.md +42 -0
  37. package/lenses/async-click-double-fire-race.md +59 -0
  38. package/lenses/async-floating-promise.md +40 -0
  39. package/lenses/async-promise-all-swallows-errors.md +42 -0
  40. package/lenses/async-unhandled-rejection-in-handler.md +42 -0
  41. package/lenses/auth-missing-on-state-change-route.md +60 -0
  42. package/lenses/auth-role-from-user-input.md +49 -0
  43. package/lenses/auth-timing-attack-password-compare.md +59 -0
  44. package/lenses/cookie-missing-secure-httponly.md +46 -0
  45. package/lenses/cors-wildcard-with-credentials.md +47 -0
  46. package/lenses/crypto-jwt-verify-without-algo-allowlist.md +41 -0
  47. package/lenses/crypto-math-random-for-tokens.md +53 -0
  48. package/lenses/crypto-md5-sha1-for-secrets.md +60 -0
  49. package/lenses/env-vars-read-at-module-top.md +42 -0
  50. package/lenses/eval-on-user-input.md +51 -0
  51. package/lenses/fetch-without-timeout.md +41 -0
  52. package/lenses/id-enumeration-sequential.md +49 -0
  53. package/lenses/interactive-state-decoupled-from-output.md +67 -0
  54. package/lenses/json-parse-no-try-catch.md +41 -0
  55. package/lenses/missing-rate-limit-on-auth.md +49 -0
  56. package/lenses/oauth-scope-wider-than-use.md +79 -0
  57. package/lenses/object-spread-clobber.md +44 -0
  58. package/lenses/open-redirect-from-untrusted-host.md +55 -0
  59. package/lenses/orm-drizzle-on-conflict-clobber.md +47 -0
  60. package/lenses/path-traversal-in-fs-access.md +59 -0
  61. package/lenses/pii-in-url-or-log.md +48 -0
  62. package/lenses/race-check-then-act.md +49 -0
  63. package/lenses/react-dangerously-set-inner-html.md +32 -0
  64. package/lenses/react-fetch-in-effect-without-abort.md +31 -0
  65. package/lenses/react-stale-closure-in-callback.md +28 -0
  66. package/lenses/react-state-set-in-render.md +33 -0
  67. package/lenses/react-use-effect-missing-cleanup.md +29 -0
  68. package/lenses/react-use-effect-missing-deps.md +30 -0
  69. package/lenses/regexp-from-user-input.md +43 -0
  70. package/lenses/return-shape-contract-break.md +71 -0
  71. package/lenses/secrets-logged-in-error-path.md +60 -0
  72. package/lenses/sql-migration-references-later-object.md +47 -0
  73. package/lenses/sql-string-concatenation.md +63 -0
  74. package/lenses/ssrf-server-side-fetch.md +69 -0
  75. package/lenses/supabase-comment-on-function-unqualified.md +37 -0
  76. package/lenses/supabase-function-default-public-execute.md +50 -0
  77. package/lenses/supabase-security-definer-no-search-path.md +34 -0
  78. package/lenses/supabase-single-500-on-no-match.md +65 -0
  79. package/lenses/upsert-state-column.md +57 -0
  80. package/lenses/url-not-encoded-for-user-id.md +78 -0
  81. package/lenses/use-state-default-not-functional.md +30 -0
  82. package/package.json +57 -0
  83. package/skills/openqodex/SKILL.md +141 -0
  84. package/templates/README.md +67 -0
  85. package/templates/claude-code/settings-hook.json +16 -0
  86. package/templates/cline/openqodex.md +9 -0
  87. package/templates/codex/AGENTS-section.md +8 -0
  88. package/templates/codex/hooks.json +16 -0
  89. package/templates/cursor/openqodex.mdc +15 -0
  90. package/toolchain.json +345 -0
package/docs/agents.md ADDED
@@ -0,0 +1,121 @@
1
+ # Agents
2
+
3
+ OpenQodex runs inside Claude Code, Cursor, Codex CLI and Cline. `openqodex init` installs it into each one it finds. The review then runs on the agent's own model, with no key.
4
+
5
+ ## Run init
6
+
7
+ Run it in your own terminal, not inside the agent:
8
+
9
+ ```
10
+ npx openqodex init
11
+ ```
12
+
13
+ `init` prints every file it will write and asks once. `--agent <name>` picks agents by hand: `claude-code`, `cursor`, `codex`, `cline` or `all`. `--dry-run` prints the plan and writes nothing.
14
+
15
+ ## User scope and project scope
16
+
17
+ The default is user scope. `init` writes into your home folder, so one install works in every repository. A file it must put inside a repository is added to `.git/info/exclude`, so `git status` does not change.
18
+
19
+ `--project` writes the files into the repository instead, for a team to commit. Run it inside a git repository.
20
+
21
+ ## The launcher
22
+
23
+ In user scope, the push gate hooks call a launcher, not npx. `init` copies the package to `~/.openqodex/runtime/<version>/` and checks the copy runs. It then writes `~/.openqodex/bin/openqodex`, a small script that runs that copy with your Node. The hooks call that script by its full path, so they do not depend on npx or your `PATH`.
24
+
25
+ In project scope, the hooks call `npx -y openqodex@<version>`, because the launcher path would not exist on a teammate's machine.
26
+
27
+ ## Claude Code
28
+
29
+ | What | User scope | Project scope |
30
+ |---|---|---|
31
+ | Skill | `~/.claude/skills/openqodex/SKILL.md` | `.claude/skills/openqodex/SKILL.md` |
32
+ | Push gate hook | merged into `~/.claude/settings.json` | merged into `.claude/settings.json` |
33
+
34
+ The hook is one `PreToolUse` entry. It matches the `Bash` tool and runs only for `git push` commands. It calls `openqodex hook check`.
35
+
36
+ ## Codex CLI
37
+
38
+ | What | User scope | Project scope |
39
+ |---|---|---|
40
+ | Skill | `~/.agents/skills/openqodex/SKILL.md` | `.agents/skills/openqodex/SKILL.md` |
41
+ | Instructions | not written | a marked section in `AGENTS.md` |
42
+ | Push gate hook | merged into `~/.codex/hooks.json` | merged into `.codex/hooks.json` |
43
+
44
+ Codex runs the hook before every shell command. `hook check` returns at once and prints nothing when the command is not a `git push`.
45
+
46
+ Codex runs a new hook only after you trust it. Open Codex, run `/hooks`, and trust the OpenQodex hook. Until then, Codex skips the push gate. A project hook also needs the project itself to be trusted in Codex.
47
+
48
+ ## Cursor
49
+
50
+ | What | User scope | Project scope |
51
+ |---|---|---|
52
+ | Skill | `~/.cursor/skills/openqodex/SKILL.md` | `.agents/skills/openqodex/SKILL.md` |
53
+ | Rule | `.cursor/rules/openqodex.mdc` in the repository, excluded from git | `.cursor/rules/openqodex.mdc` |
54
+
55
+ Cursor has no rule file in the home folder, so the rule always goes in the repository. In user scope, run `init` inside each repository where you want the rule. The rule applies to every chat and tells Cursor to review before any `git push`.
56
+
57
+ OpenQodex writes no Cursor hook. The rule asks Cursor to review, but nothing stops a push from Cursor.
58
+
59
+ ## Cline
60
+
61
+ | What | User scope | Project scope |
62
+ |---|---|---|
63
+ | Skill | `~/.cline/skills/openqodex/SKILL.md` | `.cline/skills/openqodex/SKILL.md` |
64
+ | Rule | `~/Documents/Cline/Rules/openqodex.md` | `.clinerules/openqodex.md` |
65
+
66
+ OpenQodex writes no Cline hook. The rule asks Cline to review before any `git push`.
67
+
68
+ ## What the push gate does
69
+
70
+ The gate runs in Claude Code and Codex, through the hooks above. It never approves a push for you: your agent's own permission prompt for `git push` still applies.
71
+
72
+ Without `review.block_on_severity` in `.openqodex.yaml`, the gate never stops a push:
73
+
74
+ - When a finished review of the current change has findings, it adds the finding counts and the report path. A clean review adds nothing.
75
+ - When none exists, it adds a note that the change was not reviewed and how to review it.
76
+
77
+ With `review.block_on_severity` set, the gate denies the push unless a finished review of the current change passed. The reason names the next step.
78
+
79
+ `OPENQODEX_SKIP=1` in the environment lets the push through and says so. It is your switch, not your agent's.
80
+
81
+ The hook never breaks a push by accident. When `hook check` itself fails, it prints the reason on stderr and lets the agent go on.
82
+
83
+ ## A git hook for every tool
84
+
85
+ For pushes from any tool, add a git pre-push hook to one repository:
86
+
87
+ ```
88
+ npx openqodex hook install
89
+ ```
90
+
91
+ It runs `openqodex scan` before each push, through the launcher. It stops the push only when `.openqodex.yaml` sets `review.block_on_severity` and the scan meets it. A scan that fails for its own reasons never stops the push. `init` never installs it. `cli` has the details.
92
+
93
+ ## Other ways to install
94
+
95
+ - The skill alone: `npx skills add openqodex/openqodex`. This writes the skill but no hook.
96
+ - Claude Code plugin: the repository holds a plugin marketplace with an `openqodex` plugin. The plugin carries the skill and the push gate hook. Its hook calls `npx -y openqodex@<version>`.
97
+
98
+ ## Inside a sandbox
99
+
100
+ Some agents run commands in a sandbox that cannot reach the network or write outside the project. There, the first review cannot download scanners. Each scanner reports why it was left out, and the review runs with what is available. Run this once in your own terminal to fix it:
101
+
102
+ ```
103
+ npx openqodex doctor --install
104
+ ```
105
+
106
+ The review writes its files inside the repository, in `.openqodex/`. So it works in a sandbox that can write only the project.
107
+
108
+ ## Uninstall
109
+
110
+ ```
111
+ npx openqodex init --uninstall
112
+ ```
113
+
114
+ Add `--project` to remove project files. `init` records what it wrote in `~/.openqodex/install.json`. `--uninstall` removes only what that record holds:
115
+
116
+ - A skill or rule file is removed only when it is unchanged since `init` wrote it.
117
+ - The hook entry is removed from the settings file. Other settings stay. When `init` saved a backup and nothing else changed, the backup is put back.
118
+ - The `.git/info/exclude` lines are removed.
119
+ - The launcher and the runtime copies are removed when no hook still calls them. The git pre-push hook counts as one.
120
+
121
+ Scanners stay in `~/.openqodex/tools/`. Delete that folder to remove them too.
package/docs/cli.md ADDED
@@ -0,0 +1,161 @@
1
+ # Commands
2
+
3
+ Run every command with `npx openqodex <command>`, or `openqodex <command>` when the package is installed.
4
+
5
+ ## Exit codes
6
+
7
+ - `0`: clean, or warnings only.
8
+ - `1`: a finding at or above `review.block_on_severity`. Without that key, no command exits 1.
9
+ - `2`: OpenQodex itself failed: a wrong flag, an invalid config, not a git repository, a stale review, or an internal error. `doctor` prints its table first and then exits 2.
10
+
11
+ A scanner that fails or is missing never changes the exit code. The report lists it with the reason.
12
+
13
+ ## Which change is checked
14
+
15
+ By default the change is the commits not yet pushed plus everything uncommitted, untracked files included. OpenQodex finds the base in this order:
16
+
17
+ 1. `--base <ref>`: the point where the current branch left that ref.
18
+ 2. `--uncommitted`: the last commit, `HEAD`. Only uncommitted work counts.
19
+ 3. The point where the branch left its upstream branch.
20
+ 4. The point where the branch left the remote's default branch (`origin/HEAD`).
21
+ 5. The last commit, `HEAD`.
22
+
23
+ A repository with no commits checks every file. OpenQodex never fetches from a remote.
24
+
25
+ ## Shared flags
26
+
27
+ `scan`, `review`, `doctor`, `trust` and `guide` accept these flags. `demo` accepts only `--no-color`, `--quiet`, `--verbose`, `--no-install` and `--offline`. `init` and `hook` accept none of them.
28
+
29
+ - `--cwd <dir>`: find the repository from `<dir>`. A relative `--output` path still resolves from the folder you ran the command in.
30
+ - `--config <path>`: read this config file instead of `.openqodex.yaml` at the repo root.
31
+ - `--format <terminal|markdown|json|sarif>`: the report format. The default is `terminal`. Only `scan` and `review` use it.
32
+ - `--output <file>`: write the report to `<file>` instead of stdout. Only `scan` and `review` use it.
33
+ - `--no-color`: no colour. `NO_COLOR` set in the environment does the same.
34
+ - `--quiet`: no progress lines on stderr.
35
+ - `--verbose`: print the stack when OpenQodex itself fails.
36
+ - `--no-install`: do not download missing scanners. The report lists them as not installed.
37
+ - `--offline`: no built-in scanner goes online. osv-scanner and semgrep are skipped and listed as disabled. Scanner downloads are off.
38
+
39
+ `doctor --install` together with `--offline` or `--no-install` exits 2.
40
+
41
+ Progress goes to stderr. The report goes to stdout.
42
+
43
+ `openqodex --version` prints the version. `openqodex --help` lists the commands.
44
+
45
+ ## review
46
+
47
+ ```
48
+ openqodex review [--agent | --finalize [path]] [--base <ref>] [--uncommitted] [--only <list>] [--skip <list>]
49
+ ```
50
+
51
+ - `--agent`: run the scanners, write the brief and print it. Your agent runs this.
52
+ - `--finalize [path]`: check the agent's findings and write the report. Without a path it reads `agent-findings.json` in the newest report folder. With a path it finds the run by the `change_id` in that file.
53
+ - Neither flag: the same as `scan`, plus one line on how to get the AI review from your agent.
54
+ - `--base`, `--uncommitted`: see "Which change is checked".
55
+ - `--only <list>`: run only these scanners, comma separated.
56
+ - `--skip <list>`: skip these scanners, comma separated.
57
+
58
+ A scanner name is a built-in name such as `semgrep`, or `custom:<name>` for a custom scanner.
59
+
60
+ `--finalize` exits 2 when:
61
+
62
+ - the findings file breaks the shape, naming the first wrong field;
63
+ - the change moved since the brief;
64
+ - the config changed since the brief;
65
+ - a finding cites a scanner rule or candidate that is not in this scan.
66
+
67
+ It never repairs a finding. Fix what it names, or run `review --agent` again.
68
+
69
+ ## scan
70
+
71
+ ```
72
+ openqodex scan [--base <ref>] [--uncommitted] [--only <list>] [--skip <list>]
73
+ ```
74
+
75
+ Runs the scanners on the change and prints the report. No model is involved. The git hook, the pre-commit hook and the GitHub Action run this command.
76
+
77
+ ## init
78
+
79
+ ```
80
+ openqodex init [--agent <name>]... [--project] [--yes] [--uninstall] [--dry-run]
81
+ ```
82
+
83
+ Installs OpenQodex into your coding agents.
84
+
85
+ - `--agent <name>`: `claude-code`, `cursor`, `codex`, `cline` or `all`. Repeat it for several. Without it, `init` uses every agent it finds.
86
+ - `--project`: write the files into the repository for a team to commit. The default writes them in your home folder.
87
+ - `--yes`, `-y`: do not ask. Without a terminal, `init` needs this flag.
88
+ - `--uninstall`: remove what `init` wrote. A file you edited after `init` is left in place.
89
+ - `--dry-run`: print the plan and write nothing.
90
+
91
+ `init` does not take the flags listed under "Flags every command below accepts". `agents` lists each file it writes.
92
+
93
+ ## doctor
94
+
95
+ ```
96
+ openqodex doctor [--install] [--json]
97
+ ```
98
+
99
+ Prints the Node and git versions, the repository, the config, the OpenQodex home folder and the state of each scanner. It lists custom scanners with their approval state.
100
+
101
+ - `--install`: download every scanner that fits this machine, and wait for all of them.
102
+ - `--json`: print the same facts as JSON.
103
+
104
+ `doctor` always prints its table. It then exits 2 in three cases:
105
+
106
+ - git is missing;
107
+ - the config does not load;
108
+ - the `--cwd` folder does not exist.
109
+
110
+ ## trust
111
+
112
+ ```
113
+ openqodex trust [--yes] [--list] [--revoke <name>]
114
+ ```
115
+
116
+ Approves the custom scanners in `.openqodex.yaml`. For each new or changed entry, it downloads the release asset. It shows what will run and asks yes or no.
117
+
118
+ - `--yes`: approve every pending entry without asking. Use it only for entries you have read.
119
+ - `--list`: print each custom scanner and its state: trusted, not approved, or changed since approval.
120
+ - `--revoke <name>`: remove the approval for one scanner.
121
+
122
+ Without a terminal and without `--yes`, `trust` exits 2. `custom-scanners` explains the whole step.
123
+
124
+ ## hook
125
+
126
+ ```
127
+ openqodex hook check
128
+ openqodex hook install [--force]
129
+ openqodex hook uninstall
130
+ ```
131
+
132
+ - `hook check`: the push gate. The Claude Code and Codex hooks call it before a shell command. It reads the hook's JSON on stdin. It always exits 0.
133
+ - `hook install`: add a git pre-push hook to this repository. It also sets up the launcher in `~/.openqodex/`, which the hook calls. The hook runs `scan` before each push. It stops the push only when the scan exits 1. A scan that fails for its own reasons never stops the push.
134
+ - `hook install` refuses to replace a hook it did not write. `--force` replaces it and keeps the old hook as `pre-push.openqodex.bak`.
135
+ - `hook uninstall`: remove that hook and put back the one it replaced. A hook you edited after install is left in place.
136
+
137
+ When the repository uses husky or lefthook, `hook install` writes nothing. It prints the line to add to their pre-push hook.
138
+
139
+ `init` never installs the git hook. `agents` explains the push gate.
140
+
141
+ ## guide
142
+
143
+ ```
144
+ openqodex guide [topic]
145
+ ```
146
+
147
+ Prints the skill without a topic. With a topic, it prints that page of these docs. An unknown topic lists the topics and exits 2.
148
+
149
+ ## demo
150
+
151
+ ```
152
+ openqodex demo [dir]
153
+ ```
154
+
155
+ Builds the demo repository in `<dir>`, or in a new temporary folder. A relative `<dir>` resolves from the folder you run the command in. The folder must be empty or new. The demo commits a clean baseline, then adds a change with planted bugs and leaves it uncommitted. It scans that change and prints the report. When some scanners are still installing, it says so and asks you to run `scan` again. The secret in the demo is generated each time and works nowhere.
156
+
157
+ ## Environment variables
158
+
159
+ - `OPENQODEX_HOME`: where OpenQodex keeps scanners, the launcher and approvals. The default is `~/.openqodex`.
160
+ - `OPENQODEX_SKIP=1`: the push gate lets the push through and says so. It is your switch, not your agent's.
161
+ - `NO_COLOR`: no colour in the terminal report.
package/docs/config.md ADDED
@@ -0,0 +1,165 @@
1
+ # Configuration
2
+
3
+ OpenQodex reads `.openqodex.yaml` at the root of the repository. Every key is optional. With no file, the defaults apply, and OpenQodex warns but never blocks.
4
+
5
+ `--config <path>` reads another file instead.
6
+
7
+ An unknown key prints a warning and is ignored. A value of the wrong type stops the run with exit 2 and names the key.
8
+
9
+ ## A full example
10
+
11
+ ```yaml
12
+ version: 1
13
+ review:
14
+ block_on_severity: critical
15
+ paths:
16
+ exclude: ["vendor/**", "**/*.min.js", "*.min.js"]
17
+ disabled_rules: ["gitleaks:generic-api-key", "lens:react-*"]
18
+ include_fixtures: false
19
+ scanners:
20
+ disable: [brakeman]
21
+ custom:
22
+ - source: https://github.com/aquasecurity/trivy
23
+ run: trivy config --format sarif --output {report} {target}
24
+ ```
25
+
26
+ ## Severity
27
+
28
+ OpenQodex uses one scale: `critical`, `major`, `minor`, `nitpick`, `info`. Scanner severities map onto it:
29
+
30
+ - critical to `critical`
31
+ - high to `major`
32
+ - medium to `minor`
33
+ - low to `nitpick`
34
+ - info to `info`
35
+
36
+ ## version
37
+
38
+ `1`, the only version. Optional.
39
+
40
+ ## review.block_on_severity
41
+
42
+ One of `critical`, `major`, `minor`, `nitpick`, `info`. The default is unset.
43
+
44
+ - Unset: OpenQodex warns and never blocks. No command exits 1. The push gate never denies a push.
45
+ - Set: the verdict is `blocked` when a finding on a changed line is at or above this severity. `scan` and `review` exit 1. The push gate denies a push unless a finished review of the current change passed.
46
+
47
+ Findings outside the changed lines never count toward the verdict.
48
+
49
+ ## review.paths.exclude
50
+
51
+ A list of globs. The default is an empty list. A matching file is left out of the change. The brief does not show it, and no finding in it is kept. Scanners do not receive it as a changed file. A scanner that reads a whole project, such as brakeman or golangci-lint, may still read it.
52
+
53
+ Globs match the path from the repository root, with forward slashes:
54
+
55
+ - `*` matches any characters except `/`.
56
+ - `**` matches any characters, `/` included.
57
+ - `?` matches one character except `/`.
58
+
59
+ There is no negation and no character class.
60
+
61
+ A `**/` prefix does not match a file at the repository root. `**/*.min.js` matches `web/app.min.js` but not `app.min.js`. To match both, list `**/*.min.js` and `*.min.js`.
62
+
63
+ ## review.disabled_rules
64
+
65
+ A list of globs matched against a finding's citation, `<source>:<rule>`. A matching finding is dropped. The default is an empty list.
66
+
67
+ - `gitleaks:generic-api-key` drops one gitleaks rule.
68
+ - `semgrep:python.lang.*` drops a family of semgrep rules.
69
+ - `lens:react-*` drops agent findings that cite a review pattern whose name starts with `react-`.
70
+ - `custom:trivy:*` drops every finding of the custom scanner named `trivy`.
71
+
72
+ ## review.include_fixtures
73
+
74
+ `true` or `false`. The default is `false`.
75
+
76
+ With `false`, scanner findings in test fixtures, mocks, stubs, fakes and snapshots are dropped. A path counts when one of its folders is `fixtures`, `__fixtures__`, `mocks`, `__mocks__`, `snapshots`, `__snapshots__`, `fakes`, `stubs` or `testdata`. It also counts when the file name holds `.fixture.`, `.mock.` or `.stub.` (or their plurals), or ends in `.snap`. Test files themselves are not dropped.
77
+
78
+ ## scanners.disable
79
+
80
+ A list of built-in scanner names to switch off. The names are `semgrep`, `gitleaks`, `sqllint`, `osv-scanner`, `actionlint`, `hadolint`, `shellcheck`, `ruff`, `brakeman`, `rubocop`, `bandit`, `oxlint` and `golangci`. A disabled scanner is listed in the report as disabled.
81
+
82
+ ## scanners.custom
83
+
84
+ A list of custom scanners. Each one needs two keys:
85
+
86
+ ```yaml
87
+ scanners:
88
+ custom:
89
+ - source: https://github.com/aquasecurity/trivy
90
+ run: trivy config --format sarif --output {report} {target}
91
+ ```
92
+
93
+ A custom scanner never runs until you approve it with `openqodex trust`. `custom-scanners` explains the step.
94
+
95
+ ### source
96
+
97
+ The GitHub link of the scanner's repository, `https://github.com/<owner>/<repo>`. Required.
98
+
99
+ ### run
100
+
101
+ The command line. Required. OpenQodex splits it into words and starts the first word as the program. It never runs the line through a shell, so pipes, `&&` and `$VAR` have no effect.
102
+
103
+ Three placeholders are filled in:
104
+
105
+ - `{report}`: the file the scanner writes its report to.
106
+ - `{target}`: the files to scan. See `target`.
107
+ - `{repo}`: the repository root.
108
+
109
+ ### name
110
+
111
+ The scanner's name in the report, as `custom:<name>`. The default is the repository name from `source`. Letters, digits, dot, dash and underscore only. Two entries cannot share a name.
112
+
113
+ ### version
114
+
115
+ The release to use, such as `0.58.1`. The default is the latest release at the time you run `openqodex trust`.
116
+
117
+ ### format
118
+
119
+ `sarif` or `json-map`. The default is `sarif`. `json-map` reads any JSON report through a `map` block.
120
+
121
+ ### map
122
+
123
+ Required when `format` is `json-map`. Each value is a dotted path into the report, with `[n]` for a list index.
124
+
125
+ - `items`: the path to the list of results. `.` means the report itself is the list. Required.
126
+ - `file`: the file path in one result. Required.
127
+ - `line`: the start line. Required.
128
+ - `end_line`: the end line. Optional.
129
+ - `rule`: the rule id. Required.
130
+ - `severity`: the scanner's severity. Optional.
131
+ - `message`: the message. Required.
132
+ - `reference`: a link for the rule. Optional.
133
+ - `severity_map`: maps the scanner's severity words to `critical`, `high`, `medium`, `low` or `info`. A word not in the map reads as `medium`.
134
+
135
+ `custom-scanners` has a worked example.
136
+
137
+ ### paths
138
+
139
+ A list of globs. The scanner runs only when a changed file matches one, and receives only matching files. The default is every changed file.
140
+
141
+ ### target
142
+
143
+ `changed` or `repo`. The default is `changed`.
144
+
145
+ - `changed`: `{target}` is the changed files.
146
+ - `repo`: `{target}` is the repository root.
147
+
148
+ Either way, only findings on changed lines are kept.
149
+
150
+ ### timeout_seconds
151
+
152
+ A whole number of seconds. The default is `120`.
153
+
154
+ ### install
155
+
156
+ How OpenQodex gets the scanner. The default downloads the GitHub release asset that fits your machine. Use one of these forms instead:
157
+
158
+ - `install: path`: use the program already on your `PATH`. Nothing is downloaded.
159
+ - `install: { asset: <name> }`: the release asset to download, when OpenQodex cannot pick one.
160
+ - `install: { binary: <path> }`: the program's path inside the asset, when it is not at the top.
161
+ - `install: { sha256: <hex> }`: the expected sha256 of the asset, in lowercase hex.
162
+ - `install: { npm: <package@version> }`: install the scanner from npm.
163
+ - `install: { uv: <package==version> }`: install the scanner from PyPI through uv.
164
+
165
+ `asset`, `binary` and `sha256` combine. `npm` and `uv` stand alone.
@@ -0,0 +1,164 @@
1
+ # Custom scanners
2
+
3
+ You can add any scanner by its GitHub link. Its findings join the same report as the built-in scanners. Only findings on changed lines are kept.
4
+
5
+ A custom scanner is an arbitrary command that runs on your machine with your permissions. Only `openqodex trust` downloads a custom scanner, and it asks you first. `scan` and `review` never download one. Nothing is installed or run before you approve that exact entry.
6
+
7
+ ## Add one
8
+
9
+ Two lines in `.openqodex.yaml` at the root of your repository:
10
+
11
+ ```yaml
12
+ scanners:
13
+ custom:
14
+ - source: https://github.com/aquasecurity/trivy
15
+ run: trivy config --format sarif --output {report} {target}
16
+ ```
17
+
18
+ - `source`: the scanner's GitHub repository.
19
+ - `run`: the command line. OpenQodex splits it into words and starts the first word as the program. It never uses a shell.
20
+
21
+ Three placeholders are filled in:
22
+
23
+ - `{report}`: the file the scanner writes its report to.
24
+ - `{target}`: the changed files, or the repository root when `target: repo` is set.
25
+ - `{repo}`: the repository root.
26
+
27
+ `config` lists every optional key: `name`, `version`, `format`, `map`, `paths`, `target`, `timeout_seconds` and `install`.
28
+
29
+ ## Approve it
30
+
31
+ ```
32
+ npx openqodex trust
33
+ ```
34
+
35
+ For each entry that is new or changed, `trust` does these steps:
36
+
37
+ 1. It reads the release from the GitHub API. Without `version`, it takes the latest release.
38
+ 2. It picks the release asset for your system and CPU.
39
+ 3. It downloads the asset to a quarantine folder. Nothing is unpacked onto the tool path or run yet.
40
+ 4. It checks the asset against the project's checksum file, when the project publishes one.
41
+ 5. It prints what will run and asks yes or no.
42
+
43
+ The printout shows the name, source, version, install form, asset, download URL and sha256. It also shows the checksum check, the program path, the run line, the paths and the target. Read it before you answer.
44
+
45
+ On yes, the asset is installed and the approval is stored. On no, the scanner is skipped until you approve it.
46
+
47
+ ## How the asset is chosen
48
+
49
+ OpenQodex matches words in each asset's file name:
50
+
51
+ - system: `darwin`, `macos`, `osx`, `apple` or `linux`;
52
+ - CPU: `arm64`, `aarch64`, `x86_64`, `amd64`, `x64` or `64bit`;
53
+ - format: `.tar.gz`, `.tar.xz`, `.zip`, or a bare program.
54
+
55
+ When no asset matches, or more than one does, `trust` stops. It prints the candidates and the line to add. Name the asset by hand. `{version}`, `{os}` and `{arch}` stand for the version and the name words above, so one line fits every machine:
56
+
57
+ ```yaml
58
+ - source: https://github.com/aquasecurity/trivy
59
+ run: trivy config --format sarif --output {report} {target}
60
+ install:
61
+ asset: "trivy_{version}_{os}-{arch}.tar.gz"
62
+ ```
63
+
64
+ The name must match exactly one asset.
65
+
66
+ Other install forms:
67
+
68
+ - `install: path`: use the program on your `PATH`. Nothing is downloaded. `trust` records the program's sha256. When the program changes, the scanner is skipped until you approve it again.
69
+ - `install: { npm: <package@1.2.3> }`: install from npm. The spec must name an exact version.
70
+ - `install: { uv: <package==1.2.3> }`: install from PyPI through uv. The spec must name an exact version.
71
+
72
+ ## What the stored hash means
73
+
74
+ `trust` records the sha256 of the asset it downloaded.
75
+
76
+ When the project publishes a checksum file and the asset matched it, that hash was checked against upstream. Otherwise the stored hash is only the hash of your first download. This is trust on first use: it does not prove the first download was genuine. The printout says which of the two applies.
77
+
78
+ ## Where approvals are stored
79
+
80
+ Approvals live in `~/.openqodex/trust.json`. Each one is keyed by the repository root and a hash of the entry and the resolved asset. An approval in one repository does not cover another.
81
+
82
+ Any edit to the entry changes its hash. The scanner is then skipped as `untrusted` until you run `openqodex trust` again.
83
+
84
+ - `openqodex trust --list` shows each custom scanner and its state.
85
+ - `openqodex trust --revoke <name>` removes one approval.
86
+ - `openqodex doctor` shows the state of each custom scanner too.
87
+
88
+ ## Report formats
89
+
90
+ ### SARIF
91
+
92
+ SARIF is a standard JSON format for scanner results. Many scanners write it with a flag. It is the default `format`.
93
+
94
+ OpenQodex reads each result's rule id, file, start and end line, and message. The severity comes from a `security-severity` score. A score on the result wins over a score on its rule:
95
+
96
+ - 9 or more: critical
97
+ - 7 or more: high
98
+ - 4 or more: medium
99
+ - above 0: low
100
+ - 0: info
101
+
102
+ Without a score, the SARIF level decides: `error` is high, `warning` is medium, `note` is low, `none` is info. Anything else is medium.
103
+
104
+ ### Worked example: trivy with SARIF
105
+
106
+ trivy checks infrastructure files such as Terraform and Kubernetes manifests.
107
+
108
+ ```yaml
109
+ scanners:
110
+ custom:
111
+ - source: https://github.com/aquasecurity/trivy
112
+ run: trivy config --format sarif --output {report} {target}
113
+ paths: ["**/*.tf", "*.tf", "**/*.yaml", "*.yaml"]
114
+ target: repo
115
+ ```
116
+
117
+ - `paths` limits the scanner to changes that hold Terraform or YAML files.
118
+ - `target: repo` passes the repository root, because `trivy config` scans a folder.
119
+ - Findings appear in the report as `custom:trivy:<rule id>`.
120
+
121
+ ### json-map
122
+
123
+ For a scanner that cannot write SARIF, `json-map` reads its JSON report through a `map` block. Each value is a dotted path into the report, with `[n]` for a list index. `items` points at the list of results. The other paths are read from each result.
124
+
125
+ ### Worked example: gosec with json-map
126
+
127
+ gosec checks Go code for security problems. Its JSON report looks like this:
128
+
129
+ ```json
130
+ {
131
+ "Issues": [
132
+ { "severity": "HIGH", "rule_id": "G101", "details": "Potential hardcoded credentials",
133
+ "file": "/home/me/app/config.go", "line": "12" }
134
+ ]
135
+ }
136
+ ```
137
+
138
+ The entry:
139
+
140
+ ```yaml
141
+ scanners:
142
+ custom:
143
+ - source: https://github.com/securego/gosec
144
+ run: gosec -fmt json -out {report} -no-fail {repo}/...
145
+ paths: ["**/*.go", "*.go"]
146
+ format: json-map
147
+ map:
148
+ items: Issues
149
+ file: file
150
+ line: line
151
+ rule: rule_id
152
+ severity: severity
153
+ message: details
154
+ severity_map:
155
+ HIGH: high
156
+ MEDIUM: medium
157
+ LOW: low
158
+ ```
159
+
160
+ - `items: Issues` points at the list.
161
+ - `file` may be absolute. OpenQodex turns a path inside the repository into a repository path.
162
+ - `line` may be a number or a string of digits. A result without a file or a line is skipped.
163
+ - `severity_map` turns gosec's words into the scanner scale. A word not in the map reads as medium.
164
+ - `-no-fail` keeps gosec from exiting with an error when it finds something.
package/docs/faq.md ADDED
@@ -0,0 +1,54 @@
1
+ # FAQ
2
+
3
+ ## Do I need an API key or an account?
4
+
5
+ No. The review runs on the model your coding agent already uses. OpenQodex itself calls no model.
6
+
7
+ ## Does OpenQodex send my code anywhere?
8
+
9
+ Not through the built-in scanners. Their network use is listed in `security`: scanner downloads, Semgrep rule packs, and dependency names and versions sent to osv.dev. Your agent's model sees what your agent reads, as it always does. A custom scanner you approved does whatever its own command does.
10
+
11
+ ## Will it block my push?
12
+
13
+ Not by default. Without `review.block_on_severity` in `.openqodex.yaml`, OpenQodex only warns. Set that key to block pushes at a severity. `OPENQODEX_SKIP=1` lets one push through.
14
+
15
+ ## Why did a scanner not run?
16
+
17
+ The report lists every selected scanner with a status and a reason. The usual reasons:
18
+
19
+ - The change holds no file it reads.
20
+ - It is still downloading on first use. It joins the next run.
21
+ - It needs Ruby or Go, which OpenQodex does not install.
22
+ - The agent's sandbox cannot download it. Run `npx openqodex doctor --install` in your own terminal.
23
+
24
+ ## Why is a finding missing that the scanner reports on my whole repo?
25
+
26
+ OpenQodex keeps only findings on the lines your change adds or edits. Findings elsewhere belong to code you did not touch. A finding the agent places outside the changed lines is listed separately and never counts toward the verdict.
27
+
28
+ ## Why were findings in my test fixtures dropped?
29
+
30
+ Scanner findings in fixtures, mocks, stubs, fakes and snapshots are dropped by default, because those files hold throwaway data. Set `review.include_fixtures: true` to keep them.
31
+
32
+ ## How do I leave files out of the review?
33
+
34
+ List globs under `review.paths.exclude`. A `**/` prefix does not match a file at the repository root. To match both, list `**/x` and `x`. `config` has the rules.
35
+
36
+ ## Can I use a scanner that is not built in?
37
+
38
+ Yes, by its GitHub link. Add two lines to `.openqodex.yaml` and approve the entry with `openqodex trust`. `custom-scanners` explains how.
39
+
40
+ ## Does it change my repository?
41
+
42
+ The review writes only inside `.openqodex/`, which ignores itself in git. `git status` does not change. The built-in scanners run with fixes switched off, and their caches live outside the repository. A custom scanner you approved does whatever its own command does. `init` in user scope adds any repository file it writes to `.git/info/exclude`.
43
+
44
+ ## Where are the reports?
45
+
46
+ In `.openqodex/reviews/<time>-<id>/` in the repository: `report.md` to read, `report.json` and `report.sarif` for tools. `.openqodex/latest.json` points at the newest one.
47
+
48
+ ## Does it work on Windows?
49
+
50
+ Through WSL. OpenQodex runs on macOS and Linux.
51
+
52
+ ## How do I remove it?
53
+
54
+ `npx openqodex init --uninstall` removes what `init` wrote. Delete `~/.openqodex/` to remove the scanners as well.