opencode-codex-security 0.0.0-stage → 0.1.1
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.
- package/CONTRIBUTING.md +15 -0
- package/DISCLOSURE +19 -0
- package/LICENSE +202 -0
- package/README.md +103 -2
- package/SECURITY.md +22 -0
- package/dist/gui-experimental.cjs +17 -0
- package/docs/GLOBAL-CONFIG.md +64 -0
- package/docs/MULTI-SCAN.md +62 -0
- package/docs/PUBLISHING.md +71 -0
- package/docs/RELEASING.md +53 -0
- package/docs/SDK.md +36 -0
- package/fixtures/vulnerable/docs/ARCHITECTURE.md +2 -0
- package/fixtures/vulnerable/src/accounts.js +4 -0
- package/fixtures/vulnerable/src/diagnostics.js +5 -0
- package/fixtures/vulnerable/src/download.js +5 -0
- package/fixtures/vulnerable/src/profile.js +4 -0
- package/package.json +79 -4
- package/src/core/codex-adapter.ts +430 -0
- package/src/core/credential-guard.ts +83 -0
- package/src/core/planned-scan.ts +316 -0
- package/src/core/report-safety.ts +41 -0
- package/src/core/result-summary.ts +512 -0
- package/src/core/run-history.ts +107 -0
- package/src/core/run-telemetry.ts +359 -0
- package/src/core/scan-plan.ts +182 -0
- package/src/core/scan-service.mjs +129 -0
- package/src/core/scanner.mjs +129 -0
- package/src/core/sdk-settings.ts +123 -0
- package/src/core/security-operations.ts +598 -0
- package/src/gui.tsx +102 -0
- package/src/operation-tools.ts +247 -0
- package/src/plan-view.ts +47 -0
- package/src/plugin.ts +346 -0
- package/src/rpc.ts +124 -0
- package/src/scan-display.ts +284 -0
- package/src/security-operations-panel.tsx +551 -0
- package/src/security-panel.tsx +1003 -0
- package/src/security-styles.ts +10 -0
- package/src/security-tui-operations.tsx +343 -0
- package/src/state-order.ts +21 -0
- package/src/tui.tsx +630 -0
- package/src/types.ts +84 -0
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Use Node 26.4+ in the 26.x line (`nvm use`). Install with `npm ci --ignore-scripts`, then run `npm run check` and `npm run pack:check` before committing. After `npm run setup:local`, also run `npm run test:host`, `npm run test:plan-host` and `npm run test:package` for host/package changes. `npm run format` formats maintained files; intentionally vulnerable fixtures are excluded so their source locations stay stable.
|
|
4
|
+
|
|
5
|
+
ESLint checks JavaScript and TypeScript correctness, including unused variables. Explicit `any` remains permitted at existing host/RPC integration boundaries; this does not replace the separate TypeScript check. Prefer typed boundaries for new code.
|
|
6
|
+
|
|
7
|
+
For TUI changes, `npm run test:tui` uses the actual OpenTUI renderer with an in-memory synthetic RPC client. It requires Bun (use a version supported by `@opentui/core`). It checks the summary at a terminal-sized viewport, scan switching, technical-detail disclosure and exact-run cancellation, without starting an OpenCode service or using model credentials.
|
|
8
|
+
|
|
9
|
+
Use the isolated fixture and injected-mock host tests for development. Do not start paid SDK operations, copy sign-ins, or send repository data merely to test the plugin. Real runs require explicit authorization for the target and expected usage.
|
|
10
|
+
|
|
11
|
+
The normal installed plugin may be watched by OpenCode. Develop in a separate checkout and do not edit/reload an installation while scans or policy questions are active. Local history does not automatically resume an interrupted scan.
|
|
12
|
+
|
|
13
|
+
Never commit `.runtime/`, dependency folders, credentials, raw scan output or screenshots containing private data. Keep synthetic examples clearly labeled and never deploy them. Do not publish a package or broaden repository visibility without approval.
|
|
14
|
+
|
|
15
|
+
For CI artifacts, workplace installation, release promotion and the remaining open-source decisions, see [the release guide](docs/RELEASING.md). Run `npm run audit:runtime` as well as the normal checks. Never use `npm audit fix --force` without inspecting the compatibility and security consequences. Dependency PRs are not automatically merged.
|
package/DISCLOSURE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Dual-use security research utility: opencode-codex-security
|
|
2
|
+
|
|
3
|
+
This plugin connects OpenCode V2 to the OpenAI Codex Security SDK to scan source
|
|
4
|
+
repositories for potential vulnerabilities, draft security policies and
|
|
5
|
+
statically assess reported findings. It includes deliberately vulnerable local
|
|
6
|
+
fixtures for offline demonstrations and automated tests. It does not implement
|
|
7
|
+
exploit execution, runtime exploitation, patch application or fix verification.
|
|
8
|
+
|
|
9
|
+
Intended legitimate use is defensive security review and authorized research on
|
|
10
|
+
repositories that the operator owns or has permission to assess. SDK operations
|
|
11
|
+
send selected repository source and context to OpenAI for inference; operators
|
|
12
|
+
must have permission for that transfer and understand potential costs. Fixture
|
|
13
|
+
mode is offline and is not a real assessment. Findings require human review.
|
|
14
|
+
|
|
15
|
+
Do not use this package for unauthorized access, abuse or malicious activity.
|
|
16
|
+
The contentPolicy dual-use declaration and this disclosure must persist across
|
|
17
|
+
versions. Automated npm publication uses staging only; a maintainer must review
|
|
18
|
+
and approve each staged version with npm two-factor authentication before it is
|
|
19
|
+
publicly available. See SECURITY.md for vulnerability reporting.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
202
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,104 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Codex Security for OpenCode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Security scans, policy drafts and finding validation inside **OpenCode V2**, with a native terminal view, an experimental Desktop panel and tools for your session's model.
|
|
4
|
+
|
|
5
|
+
This is an early project, not an official OpenCode or OpenAI product. Version `0.1.0` is available on npm under [Apache 2.0](LICENSE). Later releases use stage-only npm publishing, with human 2FA approval required before each version goes live. Tested GitHub release tarballs are also available. See [npm staging](docs/PUBLISHING.md).
|
|
6
|
+
|
|
7
|
+
## What works
|
|
8
|
+
|
|
9
|
+
- Independent Standard or Deep scans, two concurrent runs per project, phase-local progress, safe worker activity, exact cancellation and private local history.
|
|
10
|
+
- Honest partial/complete cost measurements and final coverage/repository-matching details when the SDK returns them. Missing data is labeled unavailable.
|
|
11
|
+
- Model tools to prepare/start scans, list runs, inspect status/activity/results and read individual findings.
|
|
12
|
+
- Security-policy generation that pauses for your answers and returns an **unapplied draft**.
|
|
13
|
+
- Finding validation against source. This is static assessment, not runtime reproduction, and the SDK currently has **no validation cost ceiling**. It requires separate approval.
|
|
14
|
+
- Global SDK defaults plus per-scan repository, scope, instructions and documentation context. Conversation history is not silently copied.
|
|
15
|
+
|
|
16
|
+
Patch application and fix verification are not implemented. Findings arrive at scan completion, not continuously. No scan starts just because the plugin loads.
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- For development: Node.js 26.4 or newer in the 26.x line (tested on 26.8.1).
|
|
21
|
+
- For real SDK operations: Python 3.10+; Python 3.10 also needs `tomli`.
|
|
22
|
+
- SDK mode is currently POSIX-only; the credential guard deliberately rejects Windows.
|
|
23
|
+
- OpenCode V2 2.2.6; Desktop integration is experimental.
|
|
24
|
+
- For real operations: supported Codex Security account/model access and an existing dedicated sign-in. The plugin checks that dedicated credentials exist privately before constructing the SDK client. It does not perform login or create/import credentials.
|
|
25
|
+
|
|
26
|
+
SDK mode uses `@openai/codex-security` 0.9.1. Repository source and selected context are sent to OpenAI for inference. SDK cost limits are estimates, **not guaranteed billing caps**. Account quota/availability can also limit scans.
|
|
27
|
+
|
|
28
|
+
## Install a released version
|
|
29
|
+
|
|
30
|
+
Install an exact live npm version into a dedicated local consumer, or use [tested GitHub tarballs](docs/RELEASING.md):
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
mkdir security-plugin && cd security-plugin
|
|
34
|
+
npm install --save-exact --ignore-scripts opencode-codex-security@0.1.0
|
|
35
|
+
npm audit --omit=dev --audit-level=high
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`0.1.0` is the current live version. The source version `0.1.1` is prepared for staging and is not available until separately approved. A staged version or `0.0.0-stage` placeholder is not installable functionality. Point the plugin configuration below at the absolute path of `security-plugin/node_modules/opencode-codex-security`. This uses the tested local-path plugin route; no special OpenCode install command is required. Keep the lockfile and use `npm ci --ignore-scripts` for repeat installs. Use only a workplace/device authorized for the plugin and its source-data transfers.
|
|
39
|
+
|
|
40
|
+
For updates, select an exact approved version, install into a new directory, audit, and switch the configured path only when scans, approval dialogs and policy forms have finished. Keep the previous directory for rollback. Nothing updates automatically. Test prereleases explicitly by their full version rather than following a moving tag.
|
|
41
|
+
|
|
42
|
+
## Local installation
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npm ci --ignore-scripts
|
|
46
|
+
npm run check
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Add the local package to your existing OpenCode configuration without replacing unrelated settings:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"plugins": [
|
|
54
|
+
{
|
|
55
|
+
"package": "/absolute/path/to/opencode-codex-security",
|
|
56
|
+
"options": { "engine": "codex" }
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Restart/reload OpenCode only when no scans, pending approvals or forms are active. Open the Security panel or use `/security` in the terminal. Other useful commands include `/security-scan`, `/security-scans`, `/security-policy`, `/security-validate`, `/security-operations` and `/security-answer`.
|
|
63
|
+
|
|
64
|
+
Default package behavior without `engine: codex` is an explicitly labeled, offline fixture engine. That fixture is not a real security assessment.
|
|
65
|
+
|
|
66
|
+
## Configuration and model tools
|
|
67
|
+
|
|
68
|
+
Global settings live at `$XDG_CONFIG_HOME/opencode/codex-security.json`, or `~/.config/opencode/codex-security.json`. No project configuration overrides are loaded. Per-scan mode wins over the global Standard/Deep fallback. Daybreak is a separate option, not a model or an access grant.
|
|
69
|
+
|
|
70
|
+
See [global configuration](docs/GLOBAL-CONFIG.md), [scan lifecycle and tools](docs/MULTI-SCAN.md), [SDK integration](docs/SDK.md) and [release checklist](docs/PUBLISHING.md).
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
npm run lint # ESLint, including TypeScript
|
|
76
|
+
npm run format:check # Prettier
|
|
77
|
+
npm run typecheck
|
|
78
|
+
npm test # offline unit tests
|
|
79
|
+
npm run build # Desktop bundle
|
|
80
|
+
npm run check # all checks above
|
|
81
|
+
npm run pack:check # inspect package allowlist; no publish
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For offline integration tests in the real OpenCode host:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npm run setup:local
|
|
88
|
+
npm run test:host
|
|
89
|
+
npm run test:plan-host
|
|
90
|
+
npm run test:package # installs the exact tarball into a separate consumer
|
|
91
|
+
./scripts/opencode-local.sh --standalone
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
These launchers isolate configuration under `.runtime/`. The plan-host tests inject a mock SDK: no real inference or login. `npm run preview` is a standalone fixture design harness, not native Desktop verification.
|
|
95
|
+
|
|
96
|
+
## Tested builds and releases
|
|
97
|
+
|
|
98
|
+
CI produces an exact tested tarball with its source commit and SHA-256 checksums. A manual workflow can promote a successful main build to a GitHub prerelease at the explicitly selected repository visibility without npm tokens. See [testing and release readiness](docs/RELEASING.md) and [security reporting](SECURITY.md). The maintainer approved public GitHub distribution under Apache 2.0. npm registry publication remains a separate, human-approved step for every version.
|
|
99
|
+
|
|
100
|
+
## Verification limits
|
|
101
|
+
|
|
102
|
+
The real SDK smoke test authenticated successfully and reached discovery, then stopped at a USD 1 estimated guard. It returned no completed assessment. Mocked integration covers concurrent scans, cancellation, model tools, policy questions and static validation. Direct native Desktop inspection remains a separate verification step.
|
|
103
|
+
|
|
104
|
+
Do not commit credentials, `.runtime/`, evidence captures, scan artifacts or dependency folders. The repository ignores these; package checks also reject them. See [contributing](CONTRIBUTING.md) for the safe development workflow.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
This is an early project, not an official OpenCode or OpenAI product. Only the latest reviewed commit is supported. Do not assume its results constitute a complete assessment or that SDK cost estimates are hard billing caps.
|
|
4
|
+
|
|
5
|
+
## Report a vulnerability privately
|
|
6
|
+
|
|
7
|
+
Do not put credentials, private source, scan results or exploitable details in public issues. If GitHub shows **Security → Report a vulnerability**, use that private channel. Otherwise contact the repository owner through an existing private channel and ask for a safe reporting destination. A dedicated public reporting contact and GitHub private vulnerability reporting must be configured and verified before the repository becomes public.
|
|
8
|
+
|
|
9
|
+
Include the affected commit, OS/OpenCode/Node versions, a minimal synthetic reproduction, expected behavior and impact. Redact credentials and personal data. Do not run paid scans, access others' data or publish a proof of concept without authorization. No response-time promise or bug-bounty program is implied.
|
|
10
|
+
|
|
11
|
+
## Boundaries and supply chain
|
|
12
|
+
|
|
13
|
+
- Loading the plugin does not approve a scan. Real SDK actions require explicit authorization and existing dedicated sign-in. Never import credentials as part of a build or test.
|
|
14
|
+
- SDK operations can transmit the selected repository and context to OpenAI. Use synthetic fixtures and mock-host tests for ordinary development.
|
|
15
|
+
- Runtime/history data, credentials and evidence are excluded from both Git and package artifacts. Package checks enforce a small file allowlist; they are not a complete secret scanner.
|
|
16
|
+
- CI uses a lockfile, disables dependency lifecycle scripts, pins Actions by commit, uses read-only repository access and blocks known high/critical runtime advisories. Dependency updates require review; no auto-merge is configured.
|
|
17
|
+
- The manual release job runs no repository or dependency code with its write token. It promotes only an exact tested artifact from successful main CI, checks explicitly selected repository visibility and checksums, and does not publish to npm.
|
|
18
|
+
- Checksums detect corruption and pin bytes. They are not a signature, independent provenance or a guarantee against a compromised maintainer/dependency/runner. A package's transitive dependencies still resolve during installation.
|
|
19
|
+
|
|
20
|
+
As of November 10, 2026, `npm audit --omit=dev` reports 26 moderate advisory entries in the runtime dependency graph, rooted in `glob` filesystem traversal after cancellation and `minimatch` resource-exhaustion advisories. The affected chains include OpenCode tooling dependencies; npm reports no available fix for those root advisories. No high/critical runtime advisories were reported in that check. This is a known limitation to reassess before public release, not a claim that those dependencies are safe or unreachable. Do not apply unreviewed breaking overrides just to silence the audit.
|
|
21
|
+
|
|
22
|
+
Intentionally vulnerable code under `fixtures/vulnerable` is offline test material. Never deploy it. Native Desktop UX, real account entitlement, Windows support and complete real SDK assessments have separate verification limits documented in the README.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});let e=require("solid-js/web"),t=require("@opencode/plugin/gui"),n=require("solid-js"),r=require("@opencode/plugin/rpc"),i=require("solid-js/store");var a={type:`object`,additionalProperties:!1},o={type:`object`,properties:{mode:{type:`string`,enum:[`standard`,`deep`],description:`Optional mode for this scan. Explicit user request wins; otherwise omit to use global mode (standard by default). Do not ask when the request is clear.`},repository:{type:`string`,description:`Optional local repository path. Defaults to the known current OpenCode workspace; use an absolute path when session context is unknown. This request only, never a global target.`},scopePaths:{type:`array`,items:{type:`string`},maxItems:32,description:`Existing paths relative to repository. Omit or use [] for whole repository. No globs or traversal.`},scanPrompt:{type:`string`,maxLength:8e3,description:`Focused security instructions and relevant architecture/threat assumptions. Ask the user for missing material context. Never copy whole conversation or include secrets.`},contextFiles:{type:`array`,items:{type:`string`},maxItems:8,description:`Repository-relative text documentation to add as supporting context. No credentials or files outside repository.`}},additionalProperties:!1},s={type:`object`,properties:{kind:{type:`string`,enum:[`policy`,`validation`]},repository:{type:`string`},path:{type:`string`},contextFiles:{type:`array`,items:{type:`string`},maxItems:8},finding:{anyOf:[{type:`string`},{type:`object`}]}},required:[`kind`],additionalProperties:!1},c={type:`object`,properties:{operationId:{type:`string`}},required:[`operationId`],additionalProperties:!1},l=r.Rpc.define({id:`gill.security`,methods:{capabilities:{input:a,output:{type:`object`}},operationPrepare:{input:s,output:{type:`object`}},operationStart:{input:{type:`object`,properties:{operationId:{type:`string`},approveOpenAIInference:{type:`boolean`},approveUncappedValidation:{type:`boolean`}},required:[`operationId`],additionalProperties:!1},output:{type:`object`}},operationStatus:{input:c,output:{type:`object`}},operationList:{input:a,output:{type:`object`}},operationAnswer:{input:{type:`object`,properties:{operationId:{type:`string`},questionId:{type:`string`},answer:{type:`string`,maxLength:16384}},required:[`operationId`,`questionId`,`answer`],additionalProperties:!1},output:{type:`object`}},operationCancel:{input:c,output:{type:`object`}},prepare:{input:o,output:{type:`object`}},start:{input:{type:`object`,properties:{planId:{type:`string`},approveOpenAIInference:{type:`boolean`}},additionalProperties:!1},output:{type:`object`}},list:{input:a,output:{type:`object`}},status:{input:{type:`object`,properties:{runId:{type:`string`},planId:{type:`string`}},additionalProperties:!1},output:{type:`object`}},cancel:{input:{type:`object`,properties:{runId:{type:`string`}},additionalProperties:!1},output:{type:`object`}}},events:{changed:{schema:{type:`object`}},operationChanged:{schema:{type:`object`}}}});function u(e){return`${e.mock?`MOCK SDK TEST: no model calls or uploads.
|
|
2
|
+
`:``}Repository: ${e.repository}\nScope: ${e.scopePaths.join(`, `)||`whole repository`}\nScan prompt:\n${e.scanPrompt||`(none)`}\nContext files: ${e.contextFiles.map(e=>`${e.path} (${e.bytes} bytes, SHA-256 ${e.sha256})`).join(`; `)||`(none)`}\nSDK settings: model ${e.settings.codex.model}, reasoning ${e.settings.codex.reasoningEffort}, Daybreak ${e.settings.codex.daybreak?`on`:`off`}; mode ${e.settings.scan.mode} (${e.inputs.mode?`per-scan choice`:`global default`}), access program ${e.settings.scan.cyberAccessProgram??`Codex default`}, estimated cost stop USD ${e.settings.scan.maxCostUsd}. Account/model/Daybreak access is not verified by these settings.\nGlobal config: ${e.globalConfigFile}${e.globalConfigDigest?``:` (not present)`}\nOnly this explicit prompt and these files are added as context; conversation history is not copied. Scoped paths guide the scan, not a filesystem sandbox.\n${e.mock?`Synthetic SDK responses only.`:`Repository source and listed context go to OpenAI via dedicated ChatGPT sign-in. Local results. USD ${e.settings.scan.maxCostUsd} estimated stop threshold, not a hard cap; in-flight usage may exceed it.`}`}function d(e){if(e.server.id===`sidecar`&&f(e.directory)&&e.location?.directory===e.directory)return e.directory}function f(e){return!!e&&(e.startsWith(`/`)||/^[A-Za-z]:[\\/]/.test(e))}function p(e,t=Date.now(),n){let r=e=>!!e?.plan&&!e.planConsumed&&Date.parse(e.plan.expiresAt)>t;if(n)return e?.drafts.find(e=>e.plan?.id===n&&r(e));if(r(e?.preview))return e.preview}function m(e){if(!e||typeof e!=`object`||!(`status`in e)||!(`findings`in e)||!Array.isArray(e.findings))throw Error(`Invalid security state`);return e}var h=e=>e.replace(/[\\/]+$/,``).split(/[\\/]/).at(-1)||e||`Project`;function g(e){return e.plan?.scanPrompt?.split(`
|
|
3
|
+
`)[0].slice(0,72)||(e.plan?.scopePaths.length?e.plan.scopePaths.join(`, `):h(e.repository))}function _(e){return e.plan?.scopePaths.join(`, `)||`Whole repository`}function v(e){let t=e.replace(/\s*\((files|reviews)\)$/,``).replaceAll(`_`,` `),n={preflight:`Preparing scan`,discovery:`Reviewing source`,"attack path":`Reviewing attack paths`,"file review":`Reviewing source`,ranking:`Prioritizing findings`,validation:`Validating findings`,reporting:`Preparing report`};return n[t]?n[t]:t?t[0].toUpperCase()+t.slice(1):`Starting scan`}function y(e){let t=e.telemetry?.phaseProgress,n=t?t.reviews&&t.reviews.total>0?{...t.reviews,unit:`reviews`}:{...t.files,unit:`files`}:{...e.progress,unit:e.progress.unit??`files`};return{...n,done:Number.isFinite(n.done)?Math.max(0,n.done):0,total:Number.isFinite(n.total)?Math.max(0,n.total):0}}function b(e){return e.status===`running`&&e.telemetry?.lifecycle?.status===`completed`}function x(e){let t=e.telemetry?.lifecycle?.status;return e.status===`running`&&t!==void 0&&t!==`running`}function S(e){return e.status===`cancelled`?`Scan cancelled`:e.status===`error`?`Scan stopped`:e.status===`completed`?`Scan complete`:b(e)?`Finalizing results`:x(e)?e.telemetry?.lifecycle?.status===`failed`?`Finishing failed scan`:`Finishing cancelled scan`:e.deepProgress?.consolidating?`Consolidating deep review`:v(e.telemetry?.phaseProgress?.phase??e.phase)}function C(e){return e.status===`running`&&!x(e)&&!e.deepProgress?.consolidating}function ee(e){let{done:t,total:n}=y(e);return C(e)&&n>0&&t<=n}function te(e){let{done:t,total:n,unit:r}=y(e);return n>0?`${t} of ${n} ${r}`:t>0?`${t} ${r} completed`:`Waiting for progress details`}function w(e){let t=e.telemetry?.phaseProgress;return t?.reviews&&t.reviews.total>0&&t.files.total>0?`Current phase only · ${t.files.done} of ${t.files.total} files`:`Current phase only`}function ne(e){let t=e.deepProgress;return t?`${t.completed} deep ${t.completed===1?`pass`:`passes`} complete · ${t.active} active · cap ${t.maximum}`:``}function re(e){return x(e)&&!b(e)?`The SDK reported ${e.telemetry?.lifecycle?.status===`failed`?`a scan failure`:`cancellation`}. Waiting for the final result; this is not a completed assessment.`:b(e)?`Analysis complete. Waiting for the SDK’s final result and findings.`:`Findings will appear when the scan finishes.`}var T=e=>e>0&&e<.01?`<0.01`:e.toFixed(2);function E(e){let t=e.telemetry?.measurement;if(t){let e=t.cost;if(!e||!Number.isFinite(e.baselineUsd))return`Cost estimate unavailable`;let n=`USD ${T(e.baselineUsd)}`;return t.coverage===`partial`?`Partial estimate ${n}`:t.coverage===`unavailable`?`Last estimate ${n} · coverage unavailable`:`Estimated usage ${n}`}return e.estimatedCostUsd!==void 0&&Number.isFinite(e.estimatedCostUsd)?`Estimated usage USD ${T(e.estimatedCostUsd)} · coverage unknown`:`Cost estimate unavailable`}function ie(e){let t=e.telemetry?.measurement;if(!t)return`Usage coverage was not reported. Estimates are not invoices.`;let n=t.missingThreadCount;return t.coverage===`partial`?`Measured subtotal only${n?`; ${n} ${n===1?`session is`:`sessions are`} missing`:``}. The full estimate may be higher.`:t.coverage===`unavailable`?`The SDK could not establish usage coverage.`:`All reported sessions are included. This is a usage estimate, not an invoice.`}function ae(e){let t=e.telemetry?.measurement?.cost?.range;return t?t.max===null?`USD ${T(t.min)} or more; upper estimate unavailable`:`USD ${T(t.min)}–${T(t.max)}`:``}function D(e){return[...e.telemetry?.activity??[]].sort((e,t)=>t.updatedAt.localeCompare(e.updatedAt))}function O(e){let t=new Set;return D(e).filter(e=>{let n=e.worker??`coordinator`;return!t.has(n)&&(t.add(n),!0)})}function k(e){return e===void 0?`Coordinator`:`Worker ${e}`}function A(e,t){return e===`running`&&(t.status!==`running`||x(t)||t.deepProgress?.consolidating)?`Last reported running`:e===`running`?`Working`:e===`failed`?`Failed`:`Done`}function oe(e){let t=e.telemetry?.observedWorkers.length;return t?`${t} worker ${t===1?`session`:`sessions`} observed`:``}function j(e){let t=e.workers;return t?`${v(t.phase)}: ${t.started} started · ${t.planned} planned`:``}function M(e){let t=e.telemetry?.lastReconnect;if(!t)return``;let n=t.reason?` · ${t.reason.replaceAll(`_`,` `)}`:``;return`Last connection retry: ${t.attempt} of ${t.maxAttempts}${n}`}function se(e){let t=e.assessment?.returnedFindings;return t?t.total===null?`Returned finding counts unavailable`:[`${t.reportable??`Unknown`} reportable`,`${t.informational??`Unknown`} informational`,...t.unclassified?[`${t.unclassified} unclassified`]:[]].join(` · `):`${e.findings.length} ${e.findings.length===1?`finding`:`findings`} shown${e.skipped?` · ${e.skipped} other items omitted`:``}`}function ce(e){let t=e.assessment?.coverage;return!t||t.source===`unavailable`?`Coverage details unavailable`:[t.surfaces.total===null?`Surfaces unavailable`:`${t.surfaces.total} review ${t.surfaces.total===1?`surface`:`surfaces`}`,t.deferred.total===null?`Deferred work unavailable`:`${t.deferred.total} deferred`,t.openQuestions.total===null?`Open questions unavailable`:`${t.openQuestions.total} open ${t.openQuestions.total===1?`question`:`questions`}`].join(` · `)}function N(e){let t=e.assessment?.repositoryFindings;return t?.available?`${t.open??`Unknown`} open · ${t.closed??`Unknown`} closed · ${t.confirmedInLatestScan??`Unknown`} confirmed in this scan`:`Repository matching unavailable`}function le(e){let t=e.assessment?.coverage;if(!t)return[];let n=[],r=(e,t,r)=>{n.push({label:e,value:t.total===null?`Not reported`:t.total===0?`None reported`:`${t.total} reported${t.omitted?`; ${t.omitted} not shown`:``}`});for(let e of t.items)n.push({label:``,value:r(e)})};return r(`Review surfaces`,t.surfaces,e=>`${e.label??`Unnamed surface`} · ${e.disposition.replaceAll(`_`,` `)}${e.riskArea?` · ${e.riskArea}`:``}`),r(`Explicit exclusions`,t.explicitExclusions,e=>`${e.pattern??`Unspecified scope`}${e.reason?` · ${e.reason}`:``}`),r(`Deferred work`,t.deferred,e=>`${e.reason??`No reason reported`}${e.paths.items.length?` · ${e.paths.items.join(`, `)}`:``}`),r(`Resolved deferred work`,t.resolvedDeferred,e=>e.reason??`Resolution recorded`),r(`Open questions`,t.openQuestions,e=>e.question??`Question details unavailable`),r(`Included scope`,t.scopeIncludePaths,e=>e),r(`Excluded scope`,t.scopeExcludePaths,e=>e),r(`Review-excluded paths`,t.reviewExcludedPaths,e=>e),r(`Limitations`,t.limitations,e=>e),n}function ue(e,t,n=Date.now()){if(!e)return``;let r=Math.max(0,Math.floor(((t?Date.parse(t):n)-Date.parse(e))/1e3));return Number.isFinite(r)?r<60?`${r}s`:r<3600?`${Math.floor(r/60)}m ${r%60}s`:`${Math.floor(r/3600)}h ${Math.floor(r%3600/60)}m`:``}function P(e){return e.split(`
|
|
4
|
+
`).map(e=>e.trim()).filter(Boolean)}function F(e){return e&&typeof e==`object`&&`message`in e?String(e.message):String(e)}function de(e){return`${e.title}\nSeverity: ${e.severity} · ${e.cwe}\nLocation: ${e.path}:${e.line}\n\n${e.summary}\n\nEvidence:\n${e.evidence}\n\nSuggested remediation:\n${e.remediation}`}var I=(0,e.template)(`<button class=gs-button>New `),L=(0,e.template)(`<p class="gs-notice gs-error"role=alert>`),R=(0,e.template)(`<label>Component directory <span class=gs-muted>Optional</span><input aria-label="Policy component directory"placeholder=src/api><span class=gs-help>Relative to the repository. Leave blank for the whole project.`),z=(0,e.template)(`<button class=gs-button type=button>Back`),B=(0,e.template)(`<form class=gs-form><label>Target repository<input aria-label="Operation repository"required><span class=gs-help></span></label><details><summary>Supporting documents</summary><label>Context files<textarea aria-label="Operation context files"placeholder=docs/architecture.md></textarea><span class=gs-help>One repository-relative text document per line. Do not include secrets.</span></label></details><div class=gs-actions><button class="gs-button gs-primary"type=submit></button></div><p class="gs-muted gs-small">Preparation makes no model calls.`),V=(0,e.template)(`<ul class=gs-run-list aria-label="Recent operations">`),fe=(0,e.template)(`<section><div class=gs-section-heading><h3></h3></div><p class="gs-muted gs-small">`),pe=(0,e.template)(`<label>Finding to assess<textarea aria-label="Finding to assess"required maxlength=16000 placeholder="Describe the suspected issue, location, and evidence."></textarea><span class=gs-help>Use finding text or JSON. Do not include credentials.`),me=(0,e.template)(`<li><button class=gs-run-button><div class=gs-run-top><span class=gs-run-title></span><span class=gs-status></span></div><div class=gs-run-target>Target: </div><div class=gs-run-meta> · `),he=(0,e.template)(`<dt>Component`),ge=(0,e.template)(`<dd>`),_e=(0,e.template)(`<dt>Supporting documents`),ve=(0,e.template)(`<details class=gs-advanced><summary>Finding to assess</summary><pre>`),H=(0,e.template)(`<p>Mock operation only. No model calls or source uploads.`),ye=(0,e.template)(`<label class=gs-check><input type=checkbox> I approve validation without a cost ceiling.`),U=(0,e.template)(`<p class=gs-error>This preview expired. Prepare a new one before starting.`),W=(0,e.template)(`<div class=gs-review><h3>Review </h3><dl><dt>Repository</dt><dd></dd></dl><div class=gs-approval></div><details class=gs-advanced><summary>Model settings</summary><p> · <!> reasoning · Daybreak </p><p class=gs-muted>Account and model access are not verified. </p></details><div class=gs-actions><button class="gs-button gs-primary"></button><button class=gs-button>Edit request`),G=(0,e.template)(`<span>Estimated usage USD `),be=(0,e.template)(`<div class=gs-facts><span>`),K=(0,e.template)(`<button class=gs-button>Cancel this operation`),xe=(0,e.template)(`<p class=gs-notice>Cancelled. No completed assessment is available.`),Se=(0,e.template)(`<article class=gs-detail aria-label="Selected operation"><div class=gs-detail-title><h3></h3><p>Target: </p></div><details class=gs-advanced><summary>Operation details</summary><p class=gs-id></p><p class=gs-muted> questions answered. Operation history is kept for this server session.`),q=(0,e.template)(`<p>Repository source<!> and selected context will be sent to OpenAI.`),Ce=(0,e.template)(`<p><strong>`),we=(0,e.template)(`<p>`),Te=(0,e.template)(`<form class="gs-form gs-questions"><h3>Your input is needed</h3><ol></ol><label>Your answer<textarea aria-label="Answer security questions"maxlength=8000 required></textarea><span class=gs-help>Answer each question, or say what is unknown. Your answer goes to OpenAI for this operation; do not include secrets.</span></label><button class="gs-button gs-primary"type=submit>Send answer and continue`),J=(0,e.template)(`<li>`),Ee=(0,e.template)(`<p class="gs-muted gs-small">Draft only. Review it before applying. No repository files were changed.`),De=(0,e.template)(`<p class=gs-notice>This preview is truncated. Read the full local SDK artifact before relying on it.`),Oe=(0,e.template)(`<section class=gs-operation-result><h3></h3><pre>`),ke=(0,e.template)(`<p><strong></strong> · Static review, not runtime reproduction.`),Ae=(0,e.template)(`<p class="gs-muted gs-small">`);function je(t){let[r,a]=(0,i.createStore)({operations:[]}),[o,s]=(0,n.createSignal)(),[c,l]=(0,n.createSignal)(!0),[u,d]=(0,n.createSignal)(t.seed?.repository??t.directory),[f,p]=(0,n.createSignal)(``),[m,g]=(0,n.createSignal)(``),[_,y]=(0,n.createSignal)(t.seed?.finding??``),[b,x]=(0,n.createSignal)(!!t.seed),[S,C]=(0,n.createSignal)(``),[ee,te]=(0,n.createSignal)(!1),[w,ne]=(0,n.createSignal)(!1),[re,T]=(0,n.createSignal)(``),[E,ie]=(0,n.createSignal)(Date.now()),[ae,D]=(0,n.createSignal)(!1),O=!1,k=!1,A=0,oe=e=>{A++,d(e?.repository??t.directory),x(!!e),y(e?.finding??``),p(``),g(``),C(``),te(!1),T(``),s(void 0),l(!0)};(0,n.createEffect)((0,n.on)(()=>[t.kind,t.directory,t.seed],()=>oe(t.seed),{defer:!0}));let j=(0,n.createMemo)(()=>r.operations.filter(e=>e.kind===t.kind)),M=(0,n.createMemo)(()=>j().find(e=>e.operationId===o())??j().find(e=>e.status===`awaiting_answers`)??j()[0]),se=()=>t.kind===`policy`?`Policy draft`:`Finding validation`;(0,n.createEffect)(()=>{M()?.question?.id,C(``)}),(0,n.createEffect)(()=>{M()?.operationId,te(!1)});let ce=async()=>{if(!(O||k)){k=!0;try{let e=await t.port.list(),n=Array.isArray(e)?e:e?.operations;if(!Array.isArray(n))throw Error(`Could not read security operations.`);O||(a(`operations`,(0,i.reconcile)(n,{key:`operationId`,merge:!0})),ae()||(D(!0),!t.seed&&n.some(e=>e.kind===t.kind)&&l(!1)))}catch(e){O||T(F(e))}finally{k=!1}}},N=async e=>{if(!(w()||O)){ne(!0),T(``);try{await e(),O||await ce()}catch(e){O||T(F(e))}finally{O||ne(!1)}}},le=e=>{e.preventDefault();let n=A;N(async()=>{let e=await t.port.prepare({kind:t.kind,repository:u().trim()||t.directory,contextFiles:P(m()),...t.kind===`policy`?f().trim()?{path:f().trim()}:{}:{finding:_()}});!O&&n===A&&(s(e.operationId),a(`operations`,(0,i.reconcile)([e,...r.operations.filter(t=>t.operationId!==e.operationId)],{key:`operationId`,merge:!0})),l(!1))})},de=e=>void N(()=>t.port.start({operationId:e.operationId,approveOpenAIInference:!0,...e.kind===`validation`?{approveUncappedValidation:!0}:{}}));ce();let je=setInterval(()=>{ie(Date.now()),ce()},1500);return(0,n.onCleanup)(()=>{O=!0,clearInterval(je)}),(()=>{var r=fe(),i=r.firstChild,a=i.firstChild,o=i.nextSibling;return(0,e.insert)(a,se),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return!c()},get children(){var n=I();return n.firstChild,n.$$click=()=>oe(),(0,e.insert)(n,()=>t.kind===`policy`?`draft`:`validation`,null),(0,e.effect)(()=>n.disabled=w()),n}}),null),(0,e.insert)(o,()=>t.kind===`policy`?`Build security guidance from the repository, with questions when context is missing. Draft only: existing files stay unchanged.`:`Assess a finding against the source. This does not run an exploit or prove runtime reproduction.`),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return re()},get children(){var t=L();return(0,e.insert)(t,re),t}}),null),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return c()},get children(){var r=B(),i=r.firstChild,a=i.firstChild.nextSibling,o=a.nextSibling,s=i.nextSibling,c=s.firstChild.nextSibling.firstChild.nextSibling,h=s.nextSibling,v=h.firstChild;return r.addEventListener(`submit`,le),a.$$input=e=>{d(e.currentTarget.value),p(``),g(``),y(``),x(!1)},(0,e.insert)(o,()=>b()?`Starts from the selected finding’s repository. Check the target before review.`:`Defaults to this workspace. Changing it affects only this operation.`),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return t.kind===`policy`},get fallback(){return(()=>{var t=pe(),n=t.firstChild.nextSibling;return n.$$input=e=>y(e.currentTarget.value),(0,e.effect)(()=>n.disabled=w()),(0,e.effect)(()=>n.value=_()),t})()},get children(){var t=R(),n=t.firstChild.nextSibling.nextSibling;return n.$$input=e=>p(e.currentTarget.value),(0,e.effect)(()=>n.disabled=w()),(0,e.effect)(()=>n.value=f()),t}}),s),c.$$input=e=>g(e.currentTarget.value),(0,e.insert)(v,(()=>{var n=(0,e.memo)(()=>!!w());return()=>n()?`Preparing…`:`Review `+(t.kind===`policy`?`policy draft`:`validation`)})()),(0,e.insert)(h,(0,e.createComponent)(n.Show,{get when(){return j().length},get children(){var t=z();return t.$$click=()=>l(!1),(0,e.effect)(()=>t.disabled=w()),t}}),null),(0,e.effect)(e=>{var t=w(),n=w(),r=w();return t!==e.e&&(a.disabled=e.e=t),n!==e.t&&(c.disabled=e.t=n),r!==e.a&&(v.disabled=e.a=r),e},{e:void 0,t:void 0,a:void 0}),(0,e.effect)(()=>a.value=u()),(0,e.effect)(()=>c.value=m()),r}}),null),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return j().length>1},get children(){var t=V();return(0,e.insert)(t,(0,e.createComponent)(n.For,{get each(){return j()},children:t=>(()=>{var n=me(),r=n.firstChild,i=r.firstChild,a=i.firstChild,o=a.nextSibling,c=i.nextSibling;c.firstChild;var u=c.nextSibling,d=u.firstChild;return r.$$click=()=>{s(t.operationId),l(!1)},(0,e.insert)(a,()=>h(t.repository),null),(0,e.insert)(a,(()=>{var n=(0,e.memo)(()=>!!t.plan.path);return()=>n()?` / `+t.plan.path:``})(),null),(0,e.insert)(o,(()=>{var n=(0,e.memo)(()=>t.status===`awaiting_answers`);return()=>n()?`Needs your answer`:v(t.status)})()),(0,e.insert)(c,()=>t.repository,null),(0,e.insert)(u,()=>new Date(t.createdAt).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`}),d),(0,e.insert)(u,()=>t.operationId.slice(0,8),null),(0,e.effect)(n=>{var i=M()?.operationId===t.operationId,a=t.status;return i!==n.e&&(0,e.setAttribute)(r,`aria-pressed`,n.e=i),a!==n.t&&(0,e.setAttribute)(o,`data-status`,n.t=a),n},{e:void 0,t:void 0}),n})()})),t}}),null),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>!c())()&&M()},children:r=>(()=>{var i=Se(),a=i.firstChild,o=a.firstChild,s=o.nextSibling;s.firstChild;var c=a.nextSibling,u=c.firstChild.nextSibling,f=u.nextSibling,m=f.firstChild;return(0,e.insert)(o,()=>v(r().phase)),(0,e.insert)(s,()=>r().repository,null),(0,e.insert)(s,(()=>{var t=(0,e.memo)(()=>!!r().plan.path);return()=>t()?` / `+r().plan.path:``})(),null),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().status===`prepared`},get children(){var i=W(),a=i.firstChild;a.firstChild;var o=a.nextSibling,s=o.firstChild.nextSibling,c=o.nextSibling,u=c.nextSibling,f=u.firstChild.nextSibling,m=f.firstChild,h=m.nextSibling;h.nextSibling;var _=f.nextSibling;_.firstChild;var v=u.nextSibling,b=v.firstChild,x=b.nextSibling;return(0,e.insert)(a,()=>t.kind===`policy`?`policy generation`:`source validation`,null),(0,e.insert)(s,()=>r().repository),(0,e.insert)(o,(0,e.createComponent)(n.Show,{get when(){return r().plan.path},get children(){return[he(),(()=>{var t=ge();return(0,e.insert)(t,()=>r().plan.path),t})()]}}),null),(0,e.insert)(o,(0,e.createComponent)(n.Show,{get when(){return r().plan.contextFiles.length},get children(){return[_e(),(()=>{var t=ge();return(0,e.insert)(t,()=>r().plan.contextFiles.map(e=>e.path).join(`, `)),t})()]}}),null),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().kind===`validation`},get children(){var t=ve(),n=t.firstChild.nextSibling;return(0,e.insert)(n,()=>r().plan.finding),t}}),c),(0,e.insert)(c,(0,e.createComponent)(n.Show,{get when(){return r().plan.mock},get fallback(){return[(()=>{var t=q(),n=t.firstChild.nextSibling;return n.nextSibling,(0,e.insert)(t,()=>r().kind===`validation`?`, the finding,`:``,n),t})(),(()=>{var t=Ce(),n=t.firstChild;return(0,e.insert)(n,()=>r().plan.costNotice),t})(),(()=>{var t=we();return(0,e.insert)(t,()=>r().kind===`policy`?`A policy draft will be returned for review. No files will be applied.`:`This is static source assessment, not runtime reproduction. No patch will be applied.`),t})()]},get children(){return H()}})),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>r().kind===`validation`)()&&!r().plan.mock},get children(){var t=ye(),n=t.firstChild;return n.addEventListener(`change`,e=>te(e.currentTarget.checked)),(0,e.effect)(()=>n.checked=ee()),t}}),u),(0,e.insert)(f,()=>r().plan.settings.codex.model,m),(0,e.insert)(f,()=>r().plan.settings.codex.reasoningEffort,h),(0,e.insert)(f,()=>r().plan.settings.codex.daybreak?`on`:`off`,null),(0,e.insert)(_,()=>r().plan.globalConfigFile,null),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return Date.parse(r().plan.expiresAt)<=E()},get children(){return U()}}),v),b.$$click=()=>de(r()),(0,e.insert)(b,(()=>{var t=(0,e.memo)(()=>!!w());return()=>t()?`Starting…`:r().plan.mock?`Start mock operation`:`Approve and start`})()),x.$$click=()=>{d(r().repository),p(r().plan.path??``),g(r().plan.contextFiles.map(e=>e.path).join(`
|
|
5
|
+
`)),y(r().plan.finding??``),l(!0)},(0,e.effect)(e=>{var t=w()||Date.parse(r().plan.expiresAt)<=E()||r().kind===`validation`&&!r().plan.mock&&!ee(),n=w();return t!==e.e&&(b.disabled=e.e=t),n!==e.t&&(x.disabled=e.t=n),e},{e:void 0,t:void 0}),i}}),c),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().status===`running`||r().status===`awaiting_answers`},get children(){var t=be(),i=t.firstChild;return(0,e.insert)(i,()=>ue(r().startedAt,null,E())),(0,e.insert)(t,(0,e.createComponent)(n.Show,{get when(){return r().estimatedCostUsd!==void 0},get children(){var t=G();return t.firstChild,(0,e.insert)(t,()=>(r().estimatedCostUsd??0).toFixed(2),null),t}}),null),t}}),c),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().question},children:i=>(()=>{var a=Te(),o=a.firstChild.nextSibling,s=o.nextSibling,c=s.firstChild.nextSibling,l=s.nextSibling;return a.addEventListener(`submit`,e=>{e.preventDefault();let n=i();N(()=>t.port.answer({operationId:r().operationId,questionId:n.id,answer:S()}))}),(0,e.insert)(o,(0,e.createComponent)(n.For,{get each(){return i().questions},children:t=>(()=>{var n=J();return(0,e.insert)(n,t),n})()})),c.$$input=e=>C(e.currentTarget.value),(0,e.effect)(e=>{var t=w(),n=w()||!S().trim();return t!==e.e&&(c.disabled=e.e=t),n!==e.t&&(l.disabled=e.t=n),e},{e:void 0,t:void 0}),(0,e.effect)(()=>c.value=S()),a})()}),c),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().status===`running`||r().status===`awaiting_answers`},get children(){var n=K();return n.$$click=()=>void N(()=>t.port.cancel({operationId:r().operationId})),(0,e.effect)(()=>n.disabled=w()),n}}),c),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().error},get children(){var t=L();return(0,e.insert)(t,()=>r().error),t}}),c),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().status===`cancelled`},get children(){return xe()}}),c),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().result},children:t=>(()=>{var r=Oe(),i=r.firstChild,a=i.nextSibling;return(0,e.insert)(i,()=>t().kind===`policy`?`Policy draft`:`Source assessment`),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return t().kind===`policy`},get fallback(){return(()=>{var n=ke(),r=n.firstChild;return(0,e.insert)(r,()=>v(t().disposition)),n})()},get children(){return Ee()}}),a),(0,e.insert)(a,(()=>{var n=(0,e.memo)(()=>t().kind===`policy`);return()=>n()?t().content:t().report})()),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return t().kind===`policy`},get children(){return(0,e.createComponent)(n.For,{get each(){return t().reviewNotes},children:t=>(()=>{var n=Ae();return(0,e.insert)(n,t),n})()})}}),null),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return t().truncated},get children(){return De()}}),null),r})()}),c),(0,e.insert)(u,()=>r().operationId),(0,e.insert)(f,()=>r().answeredQuestionCount,m),i})()}),null),(0,e.effect)(()=>(0,e.setAttribute)(r,`aria-label`,se())),r})()}(0,e.delegateEvents)([`click`,`input`]);var Me=`
|
|
6
|
+
.gill-security{--gs-bg:var(--background-base,#151515);--gs-panel:var(--background-raised,#202020);--gs-fg:var(--text-base,#eee);--gs-muted:var(--text-weak,#b5b5b5);--gs-line:var(--border-base,#454545);--gs-accent:#9be4c6;--gs-on-accent:#142b21;color:var(--gs-fg);background:var(--gs-bg);font-size:13px;line-height:1.5;font-family:inherit;height:100%;overflow:auto;container-type:inline-size;scrollbar-color:var(--gs-line) transparent;scrollbar-width:thin}
|
|
7
|
+
.gill-security *{box-sizing:border-box}.gill-security ::selection{background:#35634f;color:#fff}.gill-security button,.gill-security input,.gill-security textarea,.gill-security select{font:inherit;color:inherit}.gill-security button{cursor:pointer}.gill-security button:disabled{opacity:.45;cursor:not-allowed}.gill-security :focus-visible{outline:2px solid var(--gs-accent);outline-offset:3px}.gill-security input,.gill-security textarea{caret-color:var(--gs-accent)}
|
|
8
|
+
.gs-workspace{font-size:12px;color:var(--gs-muted);margin:0 0 20px}.gs-workspace>summary{cursor:pointer}.gs-workspace p{margin:8px 0;overflow-wrap:anywhere}.gs-run-target{font-size:12px;color:var(--gs-muted);margin-top:4px;overflow-wrap:anywhere}.gs-body{padding:24px;max-width:900px;margin:auto}.gs-header{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:24px}.gs-header h2{font-size:20px;font-weight:600;margin:0;letter-spacing:-.025em}.gs-muted{color:var(--gs-muted)}.gs-small{font-size:12px}.gs-header p{margin:3px 0 0;font-size:12px}.gs-button{padding:8px 13px;border:1px solid var(--gs-line);border-radius:6px;background:transparent;line-height:1.4;white-space:nowrap}.gs-button:hover:not(:disabled){background:var(--gs-panel)}.gs-button.gs-primary{background:var(--gs-accent);border-color:transparent;color:var(--gs-on-accent);font-weight:600}.gs-button.gs-primary:hover:not(:disabled){filter:brightness(1.08)}.gs-link{border:0;background:none;padding:4px 0;color:var(--gs-muted)!important;text-decoration:underline;text-underline-offset:3px}.gs-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:16px}.gs-notice{padding:10px 12px;border:1px solid var(--gs-line);border-radius:6px;font-size:12px;margin-bottom:18px}.gs-error{color:#ffb3b3;border-color:#985353;overflow-wrap:anywhere}.gs-form{padding-bottom:24px;margin-bottom:24px;border-bottom:1px solid var(--gs-line)}.gs-form h3,.gs-detail h3{font-size:15px;font-weight:600;margin:0 0 12px}.gs-form label{display:block;font-size:12px;font-weight:500;margin-bottom:14px}.gs-form input,.gs-form textarea,.gs-form select{display:block;width:100%;margin-top:6px;background:var(--gs-panel);border:1px solid var(--gs-line);padding:9px 10px;border-radius:5px;line-height:1.5}.gs-form textarea{resize:vertical;min-height:68px}.gs-form input::placeholder,.gs-form textarea::placeholder{color:var(--gs-muted)}.gs-form .gs-help{display:block;font-weight:400;color:var(--gs-muted);font-size:12px;margin-top:5px}.gs-row{display:flex;align-items:flex-start;gap:12px}.gs-row>*{flex:1;min-width:0}.gs-form details{margin:16px 0}.gs-form summary,.gs-detail summary{cursor:pointer;color:var(--gs-muted);padding:4px 0}.gs-form details[open]>summary,.gs-detail details[open]>summary{margin-bottom:12px}.gs-review{border:1px solid var(--gs-line);padding:16px;border-radius:8px}.gs-review dl,.gs-details-list{margin:0}.gs-review dt,.gs-details-list dt{color:var(--gs-muted);font-size:12px;margin-top:12px}.gs-review dt:first-child,.gs-details-list dt:first-child{margin-top:0}.gs-review dd,.gs-details-list dd{margin:3px 0 0;overflow-wrap:anywhere}.gs-review p{margin:12px 0 0}.gs-review .gs-approval{border-top:1px solid var(--gs-line);padding-top:12px;margin-top:16px;font-size:12px;color:var(--gs-muted)}.gs-section-heading{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}.gs-section-heading h3{font-size:13px;margin:0;font-weight:600}.gs-section-heading span{color:var(--gs-muted);font-size:12px}.gs-runs{margin-bottom:28px;font-size:12px}.gs-runs>summary{cursor:pointer;color:var(--gs-muted);padding:0 0 12px;border-bottom:1px solid var(--gs-line)}.gs-runs>summary span{float:right}.gs-runs[open]>summary{margin-bottom:8px}.gs-run-list{list-style:none;padding:0;margin:0;max-height:240px;overflow:auto;scrollbar-width:thin}.gs-run-list li{border-bottom:1px solid var(--gs-line)}.gs-run-button{display:block;background:none;border:0;border-radius:4px;text-align:left;padding:12px 10px;width:100%;color:inherit}.gs-run-button:hover{background:var(--gs-panel)}.gs-run-button[aria-pressed=true]{background:var(--gs-panel);box-shadow:inset 2px 0 var(--gs-accent)}.gs-run-top{display:flex;justify-content:space-between;align-items:baseline;gap:12px}.gs-run-title{font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.gs-run-meta{font-size:12px;color:var(--gs-muted);margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gs-status{font-size:11px;white-space:nowrap;flex-shrink:0;color:var(--gs-muted);text-transform:capitalize}.gs-status[data-status=running]{color:var(--gs-accent)}.gs-status[data-status=error]{color:#ffb3b3}.gs-detail{padding-top:0}.gs-detail-title{margin-bottom:24px}.gs-detail-title h3{margin:0;font-size:14px;line-height:1.5;font-weight:500;overflow-wrap:anywhere}.gs-detail-title p{margin:4px 0 0;overflow-wrap:anywhere;font-size:12px;color:var(--gs-muted)}.gs-progress-heading{display:flex;justify-content:space-between;align-items:baseline;gap:16px;margin:0 0 12px}.gs-progress-heading h3{font-size:24px;line-height:1.25;font-weight:600;letter-spacing:-.025em;margin:0}.gs-progress-heading span{font-size:12px;color:var(--gs-muted);font-variant-numeric:tabular-nums}.gs-progress{appearance:none;width:100%;height:6px;border:0;display:block;margin:16px 0 10px;background:var(--gs-line);border-radius:2px;overflow:hidden}.gs-progress::-webkit-progress-bar{background:var(--gs-line)}.gs-progress::-webkit-progress-value{background:var(--gs-accent)}.gs-progress::-moz-progress-bar{background:var(--gs-accent)}.gs-progress-meta{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;color:var(--gs-muted);font-size:12px}.gs-live-note{margin:24px 0;max-width:64ch;font-size:13px}.gs-scan-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:16px;font-variant-numeric:tabular-nums}.gs-activity{margin:0;font-size:12px}.gs-activity-list{padding:0;margin:8px 0 0;list-style:none;max-height:220px;overflow:auto;scrollbar-width:thin}.gs-activity-list li{display:flex;gap:12px;align-items:baseline;padding:6px 0;font-size:12px;overflow-wrap:anywhere}.gs-activity-list time{color:var(--gs-muted);font-variant-numeric:tabular-nums;white-space:nowrap;flex-shrink:0}.gs-activity-list li:first-child{color:var(--gs-fg)}.gs-findings{margin:28px 0 24px}.gs-findings .gs-section-heading h3{font-size:18px}.gs-findings details{padding:12px 0;border-bottom:1px solid var(--gs-line)}.gs-findings summary{color:var(--gs-fg);padding:0}.gs-severity{font-size:11px;text-transform:uppercase;margin-right:8px;color:#ebc28c}.gs-severity[data-severity=critical],.gs-severity[data-severity=high]{color:#ffb3b3}.gs-findings p{font-size:13px;margin:12px 0}.gs-findings pre{font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap;overflow-wrap:anywhere;padding:12px;background:var(--gs-panel);border-radius:5px;max-height:280px;overflow:auto}.gs-finding-location{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--gs-muted);overflow-wrap:anywhere;margin:6px 0}.gs-empty{padding:22px 0;color:var(--gs-muted);font-size:13px}.gs-empty h3{color:var(--gs-fg);margin:0 0 6px;font-size:15px}.gs-empty p{margin:0;max-width:48ch}.gs-footer{font-size:11px;color:var(--gs-muted);margin-top:24px}.gs-advanced{margin-top:0;font-size:12px}.gs-detail>.gs-advanced,.gs-detail>.gs-activity,.gs-coverage{border-top:1px solid var(--gs-line)}.gs-detail>.gs-advanced>summary,.gs-detail>.gs-activity>summary,.gs-coverage>summary{padding:14px 0;color:var(--gs-fg)}.gs-detail>.gs-advanced[open],.gs-detail>.gs-activity[open],.gs-coverage[open]{padding-bottom:16px}.gs-advanced pre{white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace}.gs-pending{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 0;margin-bottom:16px;border-bottom:1px solid var(--gs-line);font-size:12px}.gs-workers{font-size:12px;color:var(--gs-muted);padding:0;list-style:none}.gs-workers li{padding:3px 0}.gs-id{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow-wrap:anywhere}
|
|
9
|
+
.gs-tabs{display:flex;gap:20px;border-bottom:1px solid var(--gs-line);margin:-4px 0 24px}.gs-tabs button{border:0;background:none;padding:8px 0 12px;color:var(--gs-muted);font-weight:500}.gs-tabs button[aria-current=page]{color:var(--gs-fg);border-bottom:2px solid var(--gs-accent)}.gs-check{display:flex!important;align-items:flex-start;gap:8px;font-size:12px!important;margin-top:16px}.gs-check input{width:auto!important;margin-top:4px!important}.gs-questions{margin-top:20px}.gs-questions li{margin-bottom:8px}.gs-operation-result{margin-top:22px}.gs-operation-result pre{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--gs-panel);padding:14px;border-radius:6px;max-height:440px;overflow:auto;font:12px/1.65 ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
10
|
+
.gs-worker-activity{list-style:none;padding:0;margin:8px 0}.gs-worker-activity li{display:flex;justify-content:space-between;align-items:baseline;gap:16px;padding:9px 0;border-bottom:1px solid var(--gs-line);font-size:12px}.gs-activity-copy{display:flex;gap:12px;min-width:0;overflow-wrap:anywhere}.gs-activity-actor{color:var(--gs-muted);flex-shrink:0;min-width:78px}.gs-activity-state{color:var(--gs-muted);font-size:11px;text-align:right;flex-shrink:0}.gs-activity-state[data-status=failed]{color:#ffb3b3}.gs-activity-history{margin-top:12px;font-size:12px}.gs-activity-paths{display:block;color:var(--gs-muted);font-size:11px;overflow-wrap:anywhere}.gs-outcome{margin-top:24px}.gs-outcome-summary{font-size:12px;color:var(--gs-muted);margin:0 0 16px}.gs-coverage{font-size:12px;margin:0}.gs-coverage ul{padding-left:18px;margin:8px 0}.gs-coverage li{margin:5px 0}
|
|
11
|
+
@container(max-width:440px){.gs-body{padding:18px 16px}.gs-row{display:block}.gs-run-top{gap:8px}.gs-header{gap:10px}.gs-header h2{font-size:18px}.gs-run-title{max-width:65%}.gs-activity-copy{flex-direction:column;gap:2px}.gs-worker-activity li{gap:8px}.gs-activity-state{max-width:110px}.gs-progress-heading{align-items:flex-start}.gs-progress-heading h3{font-size:22px}.gs-progress-heading span{white-space:nowrap}}
|
|
12
|
+
@media(prefers-reduced-motion:no-preference){.gs-detail{animation:gs-appear .15s ease-out}@keyframes gs-appear{from{opacity:.85}to{opacity:1}}}
|
|
13
|
+
`,Ne=(0,e.template)(`<button class="gs-button gs-primary">New scan`),Pe=(0,e.template)(`<button>Policy`),Fe=(0,e.template)(`<button>Validate`),Ie=(0,e.template)(`<nav class=gs-tabs aria-label="Security actions"><button>Scans`),Le=(0,e.template)(`<div class=gs-pending><span>A policy draft needs your input.</span><button class=gs-button>Answer questions`),Re=(0,e.template)(`<div class="gs-notice gs-error"role=alert> <button class=gs-link>Retry connection`),ze=(0,e.template)(`<p class="gs-notice gs-error"role=alert>`),Be=(0,e.template)(`<button class=gs-link>Close`),Ve=(0,e.template)(`<section class=gs-form aria-label="New scan"><div class=gs-section-heading><h3>New scan`),He=(0,e.template)(`<details class=gs-runs aria-label="Scan history"><summary>Switch scan <span> recent</span></summary><p class=gs-muted>Runs recorded in this workspace. Each target is shown below.</p><ul class=gs-run-list>`),Ue=(0,e.template)(`<section class=gill-security aria-label="Security scans"><style></style><div class=gs-body><header class=gs-header><div><h2>Security</h2><p class=gs-muted>Workspace · </p></div></header><details class=gs-workspace><summary>Workspace and history</summary><p class=gs-id></p><p>History belongs to this workspace, including runs with a different target.</p><p>Global settings share model and scan defaults, not a scan target or combined history.</p></details><footer class=gs-footer>`),We=(0,e.template)(`<div class=gs-pending><span>Prepared target: </span><button class=gs-button>Review draft`),Ge=(0,e.template)(`<p class="gs-muted gs-small">Synthetic fixture test. Local pattern matching only, with no source upload or model calls.`),Ke=(0,e.template)(`<button class="gs-button gs-primary">Scan fixture`),qe=(0,e.template)(`<button type=button class=gs-link>Review prepared scan · `),Je=(0,e.template)(`<form><label>Target repository<input required aria-label=Repository><span class=gs-help>Defaults to this workspace. Changing the target clears scope and context files.</span></label><label>Scan depth<select aria-label="Scan depth"><option value=default>Use global default</option><option value=standard>Standard</option><option value=deep>Deep</option></select><span class=gs-help>Deep can take longer and cost more. This choice applies only to this scan.</span></label><label>What should this scan focus on? <span class=gs-muted>Optional</span><textarea maxlength=8000 placeholder="For example, review authorization and tenant isolation."aria-label="Scan focus"></textarea></label><details><summary>Scope and supporting documents</summary><label>Paths to review<textarea placeholder="src/auth
|
|
14
|
+
src/api"aria-label="Scope paths"></textarea><span class=gs-help>One repository-relative path per line. Leave blank for the whole repository.</span></label><label>Context files<textarea placeholder="docs/architecture.md
|
|
15
|
+
SECURITY.md"aria-label="Context files"></textarea><span class=gs-help>Optional text documents within this repository. Do not include secrets.</span></label></details><div class=gs-actions><button type=submit class="gs-button gs-primary"></button><span class="gs-muted gs-small">Nothing is sent to OpenAI yet.`),Ye=(0,e.template)(`<dt>Focus`),Y=(0,e.template)(`<dd>`),Xe=(0,e.template)(`<dt>Supporting documents`),Ze=(0,e.template)(`<p>Mock SDK test. Synthetic responses only; no model calls or source uploads.`),Qe=(0,e.template)(`<p role=status class="gs-small gs-muted"> scans are already running. Start this scan after one finishes.`),$e=(0,e.template)(`<p class="gs-small gs-error">This preview has expired. Edit and review it again.`),et=(0,e.template)(`<div class=gs-review><h3>Review this scan</h3><dl><dt>Repository</dt><dd></dd><dt>Scope</dt><dd></dd><dt>Depth</dt><dd></dd></dl><details class=gs-advanced><summary>Model and advanced settings</summary><dl class=gs-details-list><dt>Model</dt><dd> · <!> reasoning</dd><dt>Daybreak</dt><dd></dd><dt>Global configuration</dt><dd></dd><dt>Settings scope</dt><dd>Shared defaults only. This target and history belong to this workspace.</dd></dl><p class=gs-muted>Model and account access have not been verified.</p><details><summary>Full approval details</summary><pre></pre></details></details><div class=gs-approval></div><div class=gs-actions><button class="gs-button gs-primary"></button><button class=gs-button>Edit scan`),tt=(0,e.template)(`<p>Repository source and listed context will be sent to OpenAI. Results are stored locally.`),nt=(0,e.template)(`<p>Estimated cost stop: <strong>USD </strong> for this scan. This is not a hard cap; in-flight usage may exceed it.`),rt=(0,e.template)(`<p>Only the focus and documents above are added as context, not your conversation. Scope guides the review; it is not a filesystem sandbox.`),it=(0,e.template)(`<div class=gs-empty><h3>No scans yet</h3><p>Start a scan to review findings and follow its activity here.`),at=(0,e.template)(`<li><button class=gs-run-button><div class=gs-run-top><span class=gs-run-title></span><span class=gs-status></span></div><div class=gs-run-target>Target: </div><div class=gs-run-meta> · <!> · `),ot=(0,e.template)(`<progress class=gs-progress>`),st=(0,e.template)(`<div class=gs-progress-meta><span></span><span>`),ct=(0,e.template)(`<button class=gs-link>`),lt=(0,e.template)(`<p class=gs-live-note>`),ut=(0,e.template)(`<p class=gs-notice>Cancelled. This scan is incomplete and is not a clean result.`),dt=(0,e.template)(`<div class=gs-empty><h3>No reportable findings shown</h3><p>No findings does not guarantee security. Check coverage below.`),ft=(0,e.template)(`<section class=gs-findings aria-label="Scan findings"><div class=gs-section-heading><h3>Findings</h3><span> to review`),pt=(0,e.template)(`<dd class=gs-muted> records have matching finding IDs. <!> were not confirmed in this scan. These are repository records, not additional findings or deduplication progress.`),mt=(0,e.template)(`<p class=gs-muted>Informational and unclassified items are not shown in the four-severity findings list. Review the full local SDK result for those items.`),ht=(0,e.template)(`<section class=gs-outcome aria-label="Scan outcome"><p class=gs-outcome-summary></p><details class=gs-coverage><summary>Coverage and repository matching</summary><p></p><p class=gs-muted>Reported metadata, not proof of exhaustive review.</p><dl class=gs-details-list><dt>Repository matching</dt><dd>`),gt=(0,e.template)(`<p class="gs-muted gs-small">`),_t=(0,e.template)(`<ul class=gs-worker-activity>`),vt=(0,e.template)(`<p class="gs-muted gs-small">Scan milestones · latest first`),yt=(0,e.template)(`<ol class=gs-activity-list>`),bt=(0,e.template)(`<details class=gs-activity-history><summary>Activity history</summary><ol class=gs-activity-list>`),xt=(0,e.template)(`<dt>Estimated range`),St=(0,e.template)(`<dt>Pricing model`),Ct=(0,e.template)(`<dt>Pricing as of`),wt=(0,e.template)(`<dt>Deep review`),Tt=(0,e.template)(`<dd class=gs-muted>The cap is a maximum, not a target or completion percentage.`),Et=(0,e.template)(`<dt>Connection`),Dt=(0,e.template)(`<dt>SDK scan ID`),Ot=(0,e.template)(`<dd class=gs-id>`),kt=(0,e.template)(`<dt>Model`),At=(0,e.template)(`<p class=gs-muted>Restored from local history.`),jt=(0,e.template)(`<article class=gs-detail aria-label="Selected scan"><div class=gs-detail-title><h3></h3><p>Target: </p><p> · </p></div><div class=gs-progress-heading role=status aria-live=polite><h3></h3><span></span></div><div class=gs-scan-actions><span class="gs-muted gs-small"></span></div><details class=gs-activity aria-label="Scan activity"><summary>Activity and workers</summary></details><details class=gs-advanced><summary>Usage and scan details</summary><dl class=gs-details-list><dt>Usage estimate</dt><dd></dd><dd class=gs-muted></dd><dt>Repository</dt><dd></dd><dt>Run ID</dt><dd class=gs-id></dd><dt>Scope</dt><dd></dd><dt>Depth</dt><dd>`),Mt=(0,e.template)(`<pre>`),Nt=(0,e.template)(`<button class=gs-button>Validate this finding`),Pt=(0,e.template)(`<details><summary><span class=gs-severity></span> </summary><div class=gs-finding-location>:<!> · </div><p></p><strong class=gs-small>Suggested next step</strong><p></p><p class="gs-muted gs-small">Confidence: <!>. Review the evidence before making changes.`),Ft=(0,e.template)(`<dt>`),It=(0,e.template)(`<p class=gs-muted>`),Lt=(0,e.template)(`<li><div class=gs-activity-copy><span class=gs-activity-actor></span><span></span></div><span class=gs-activity-state>`),Rt=(0,e.template)(`<span class=gs-activity-paths>`),zt=(0,e.template)(`<li><time></time><span> · <!> · `),Bt=(0,e.template)(`<li><time></time><span>`),Vt=(0,e.template)(`<dt>Reported tokens`),Ht=(0,e.template)(`<dd> input · <!> cached input · <!> output`);function Ut(e){if(!e||typeof e!=`object`||!(`runs`in e)||!Array.isArray(e.runs))throw Error(`Could not read scan history. Reopen Security after updating the server plugin.`);let t=e;return{...t,runs:t.runs.map(m),drafts:(t.drafts??[]).map(m),maxConcurrent:t.maxConcurrent??2}}var Wt=e=>new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}),Gt=e=>e?new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`}):``;function X(t){let[r,a]=(0,i.createStore)({runs:[],drafts:[],maxConcurrent:2}),o=()=>r,s=e=>a((0,i.reconcile)(typeof e==`function`?e(r):e,{key:`runId`,merge:!0})),[c,l]=(0,n.createSignal)(!1),[d,f]=(0,n.createSignal)(`scans`),[v,b]=(0,n.createSignal)(0),[x,T]=(0,n.createSignal)(),[I,L]=(0,n.createSignal)(),[R,z]=(0,n.createSignal)(!0),[B,V]=(0,n.createSignal)(),[fe,pe]=(0,n.createSignal)(t.directory),[me,he]=(0,n.createSignal)(`default`),[ge,_e]=(0,n.createSignal)(``),[ve,H]=(0,n.createSignal)(``),[ye,U]=(0,n.createSignal)(``),[W,G]=(0,n.createSignal)(null),[be,K]=(0,n.createSignal)(``),[xe,Se]=(0,n.createSignal)(``),[q,Ce]=(0,n.createSignal)(Date.now()),we,Te,J=!1,Ee=!1,De=!1,Oe=0,ke=0,Ae=0,X=(0,n.createMemo)(()=>o().runs.filter(e=>e.status===`running`).length),Kt=(0,n.createMemo)(()=>o().runs.find(e=>e.runId===I())??o().runs[0]);(0,n.createEffect)((0,n.on)(()=>Kt()?.runId,()=>{we?.querySelectorAll(`details[open]`).forEach(e=>e.removeAttribute(`open`)),Te&&(Te.open=!1)}));let Z=(0,n.createMemo)(()=>p(o(),q())),qt=(0,n.createMemo)(()=>o().engine?.startsWith(`local-patterns`)||o().runs[0]?.engine?.startsWith(`local-patterns`)||o().drafts[0]?.engine?.startsWith(`local-patterns`)),Q=async()=>{if(Ee||J)return;Ee=!0;let e=++Oe;try{let n=Ut(await t.port.list());if(J||e<ke)return;if(ke=e,s(n),Se(``),t.operations)try{let e=await t.operations.list();J||b((e.operations??[]).filter(e=>e.status===`awaiting_answers`).length)}catch{}c()||(l(!0),!De&&n.runs.length&&z(!1))}catch(e){J||Se(F(e))}finally{Ee=!1}},$=()=>{Ae++,De=!0,V(void 0)},Jt=()=>{$(),pe(t.directory),he(`default`),_e(``),H(``),U(``),z(!0),K(``)},Yt=e=>{$(),pe(e.repository),he(e.inputs.mode??`default`),_e(e.scanPrompt),H(e.scopePaths.join(`
|
|
16
|
+
`)),U(e.contextFiles.map(e=>e.path).join(`
|
|
17
|
+
`)),V(e),z(!0),K(``)},Xt=async e=>{if(e.preventDefault(),W()||J)return;G(`prepare`),K(``);let n=Ae;try{let e={repository:fe().trim()||t.directory,scanPrompt:ge(),scopePaths:P(ve()),contextFiles:P(ye()),...me()==="default"?{}:{mode:me()}},r=m(await t.port.prepare(e));if(J)return;if(!r.plan)throw Error(`The server did not return a scan preview. Nothing was started.`);Ae===n&&V(r.plan),await Q()}catch(e){J||K(F(e))}finally{J||G(null)}},Zt=async()=>{if(!(W()||J||!B()&&!qt())){G(`start`),K(``);try{let e=B(),n=m(await t.port.start(e?{planId:e.id,approveOpenAIInference:!0}:{}));if(J)return;ke=++Oe,L(n.runId),z(!1),V(void 0),s(e=>({...e,runs:[n,...e.runs.filter(e=>e.runId!==n.runId)]})),await Q()}catch(e){J||K(F(e))}finally{J||G(null)}}},Qt=async e=>{if(!(W()||J||!e.runId)){G(`cancel`),K(``);try{await t.port.cancel({runId:e.runId}),J||await Q()}catch(e){J||K(F(e))}finally{J||G(null)}}};Q();let $t=setInterval(()=>{Ce(Date.now()),Q()},t.pollMs??1500);return(0,n.onCleanup)(()=>{J=!0,clearInterval($t)}),(()=>{var r=Ue(),i=r.firstChild,a=i.nextSibling,s=a.firstChild,l=s.firstChild.firstChild.nextSibling;l.firstChild;var p=s.nextSibling,m=p.firstChild.nextSibling,b=p.nextSibling;return(0,e.insert)(i,Me),(0,e.insert)(l,()=>h(t.directory),null),(0,e.insert)(l,(()=>{var t=(0,e.memo)(()=>X()>0);return()=>t()?` · ${X()} running`:``})(),null),(0,e.insert)(s,(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>d()===`scans`)()&&!R()},get children(){var e=Ne();return e.$$click=Jt,e}}),null),(0,e.insert)(m,()=>t.directory),(0,e.insert)(a,(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>!!(t.operations&&c()))()&&!qt()},get children(){var r=Ie(),i=r.firstChild;return i.$$click=()=>f(`scans`),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return t.operations?.capabilities?.policy??!!t.operations},get children(){var t=Pe();return t.firstChild,t.$$click=()=>f(`policy`),(0,e.insert)(t,(()=>{var t=(0,e.memo)(()=>!!v());return()=>t()?` · ${v()} waiting`:``})(),null),(0,e.effect)(()=>(0,e.setAttribute)(t,`aria-current`,d()===`policy`?`page`:void 0)),t}}),null),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return t.operations?.capabilities?.validation??!!t.operations},get children(){var t=Fe();return t.$$click=()=>{T(void 0),f(`validation`)},(0,e.effect)(()=>(0,e.setAttribute)(t,`aria-current`,d()===`validation`?`page`:void 0)),t}}),null),(0,e.effect)(()=>(0,e.setAttribute)(i,`aria-current`,d()===`scans`?`page`:void 0)),r}}),b),(0,e.insert)(a,(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>d()!==`scans`)()?d():void 0},keyed:!0,children:n=>(0,e.createComponent)(je,{kind:n,get directory(){return t.directory},get port(){return t.operations},get seed(){return x()}})}),b),(0,e.insert)(a,(0,e.createComponent)(n.Show,{get when(){return d()===`scans`},get children(){return[(0,e.createComponent)(n.Show,{get when(){return v()},get children(){var e=Le(),t=e.firstChild.nextSibling;return t.$$click=()=>f(`policy`),e}}),(0,e.createComponent)(n.Show,{get when(){return xe()},get children(){var t=Re(),n=t.firstChild,r=n.nextSibling;return(0,e.insert)(t,xe,n),r.$$click=()=>void Q(),t}}),(0,e.createComponent)(n.Show,{get when(){return o().historyError},get children(){var t=ze();return(0,e.insert)(t,()=>o().historyError),t}}),(0,e.createComponent)(n.Show,{get when(){return be()},get children(){var t=ze();return(0,e.insert)(t,be),t}}),(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>!R())()&&Z()?.plan},children:t=>(()=>{var t=We(),n=t.firstChild;n.firstChild;var r=n.nextSibling;return(0,e.insert)(n,()=>Z()?.plan?.repository,null),r.$$click=()=>Yt(Z().plan),t})()}),(0,e.createComponent)(n.Show,{get when(){return R()},get children(){var t=Ve(),r=t.firstChild;return r.firstChild,(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return o().runs.length},get children(){var t=Be();return t.$$click=()=>z(!1),(0,e.effect)(()=>t.disabled=!!W()),t}}),null),(0,e.insert)(t,(0,e.createComponent)(n.Show,{get when(){return!qt()},get fallback(){return[Ge(),(()=>{var t=Ke();return t.$$click=()=>void Zt(),(0,e.effect)(()=>t.disabled=!!W()),t})()]},get children(){return(0,e.createComponent)(n.Show,{get when(){return B()},get fallback(){return(()=>{var t=Je(),r=t.firstChild,i=r.firstChild.nextSibling,a=r.nextSibling,o=a.firstChild.nextSibling,s=a.nextSibling,l=s.firstChild.nextSibling.nextSibling,u=s.nextSibling,d=u.firstChild.nextSibling,f=d.firstChild.nextSibling,p=d.nextSibling.firstChild.nextSibling,m=u.nextSibling.firstChild;return t.addEventListener(`submit`,e=>void Xt(e)),(0,e.insert)(t,(0,e.createComponent)(n.Show,{get when(){return Z()?.plan},get children(){var t=qe();return t.firstChild,t.$$click=()=>Yt(Z().plan),(0,e.insert)(t,()=>Z()?.plan?.repository,null),t}}),r),i.$$input=e=>{$(),pe(e.currentTarget.value),H(``),U(``)},(0,e.setAttribute)(i,`spellcheck`,!1),o.addEventListener(`change`,e=>{$(),he(e.currentTarget.value)}),l.$$input=e=>{$(),_e(e.currentTarget.value)},f.$$input=e=>{$(),H(e.currentTarget.value)},p.$$input=e=>{$(),U(e.currentTarget.value)},(0,e.insert)(m,()=>W()===`prepare`?`Preparing preview…`:`Review scan`),(0,e.effect)(()=>m.disabled=!!W()||!c()),(0,e.effect)(()=>i.value=fe()),(0,e.effect)(()=>o.value=me()),(0,e.effect)(()=>l.value=ge()),(0,e.effect)(()=>f.value=ve()),(0,e.effect)(()=>p.value=ye()),t})()},children:t=>(()=>{var r=et(),i=r.firstChild.nextSibling,a=i.firstChild.nextSibling,s=a.nextSibling.nextSibling,c=s.nextSibling.nextSibling,l=i.nextSibling,d=l.firstChild.nextSibling,f=d.firstChild.nextSibling,p=f.firstChild,m=p.nextSibling;m.nextSibling;var h=f.nextSibling.nextSibling,g=h.nextSibling.nextSibling,_=d.nextSibling.nextSibling.firstChild.nextSibling,v=l.nextSibling,y=v.nextSibling,b=y.firstChild,x=b.nextSibling;return(0,e.insert)(a,()=>t().repository),(0,e.insert)(s,()=>t().scopePaths.join(`, `)||`Whole repository`),(0,e.insert)(c,()=>t().settings.scan.mode===`deep`?`Deep`:`Standard`),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return t().scanPrompt},get children(){return[Ye(),(()=>{var n=Y();return(0,e.insert)(n,()=>t().scanPrompt),n})()]}}),null),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return t().contextFiles.length},get children(){return[Xe(),(()=>{var n=Y();return(0,e.insert)(n,()=>t().contextFiles.map(e=>e.path).join(`, `)),n})()]}}),null),(0,e.insert)(f,()=>t().settings.codex.model,p),(0,e.insert)(f,()=>t().settings.codex.reasoningEffort,m),(0,e.insert)(h,()=>t().settings.codex.daybreak?`On`:`Off`),(0,e.insert)(g,()=>t().globalConfigFile),(0,e.insert)(_,()=>u(t())),(0,e.insert)(v,(0,e.createComponent)(n.Show,{get when(){return t().mock},get fallback(){return[tt(),(()=>{var n=nt(),r=n.firstChild.nextSibling;return r.firstChild,(0,e.insert)(r,()=>t().settings.scan.maxCostUsd,null),n})(),rt()]},get children(){return Ze()}})),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return X()>=o().maxConcurrent},get children(){var t=Qe(),n=t.firstChild;return(0,e.insert)(t,()=>o().maxConcurrent,n),t}}),y),(0,e.insert)(r,(0,e.createComponent)(n.Show,{get when(){return Date.parse(t().expiresAt)<=q()},get children(){return $e()}}),y),b.$$click=()=>void Zt(),(0,e.insert)(b,(()=>{var n=(0,e.memo)(()=>W()===`start`);return()=>n()?`Starting…`:t().mock?`Start mock scan`:`Approve and start`})()),x.$$click=()=>V(void 0),(0,e.effect)(e=>{var n=!!W()||X()>=o().maxConcurrent||Date.parse(t().expiresAt)<=q(),r=!!W();return n!==e.e&&(b.disabled=e.e=n),r!==e.t&&(x.disabled=e.t=r),e},{e:void 0,t:void 0}),r})()})}}),null),t}}),(0,e.createComponent)(n.Show,{get when(){return o().runs.length},get fallback(){return(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>!!c())()&&!R()},get children(){return it()}})},get children(){var t=He(),r=t.firstChild,i=r.firstChild.nextSibling,a=i.firstChild,s=r.nextSibling.nextSibling;return(0,e.use)(e=>Te=e,t),(0,e.insert)(i,()=>o().runs.length,a),(0,e.insert)(s,(0,e.createComponent)(n.For,{get each(){return o().runs},children:t=>(()=>{var n=at(),r=n.firstChild,i=r.firstChild,a=i.firstChild,o=a.nextSibling,s=i.nextSibling;s.firstChild;var c=s.nextSibling,l=c.firstChild,u=l.nextSibling;return u.nextSibling,r.$$click=()=>L(t.runId),(0,e.insert)(a,()=>g(t)),(0,e.insert)(o,(()=>{var n=(0,e.memo)(()=>t.status===`error`);return()=>n()?`Failed`:t.status})()),(0,e.insert)(s,()=>t.repository,null),(0,e.insert)(c,()=>t.mode??t.plan?.settings.scan.mode??`Fixture`,l),(0,e.insert)(c,()=>_(t),u),(0,e.insert)(c,()=>Gt(t.startedAt),null),(0,e.insert)(c,(()=>{var n=(0,e.memo)(()=>t.status===`completed`);return()=>n()?` · ${t.findings.length} ${t.findings.length===1?`finding`:`findings`} shown`:``})(),null),(0,e.effect)(n=>{var i=Kt()?.runId===t.runId,a=t.status;return i!==n.e&&(0,e.setAttribute)(r,`aria-pressed`,n.e=i),a!==n.t&&(0,e.setAttribute)(o,`data-status`,n.t=a),n},{e:void 0,t:void 0}),n})()})),t}}),(0,e.createComponent)(n.Show,{get when(){return Kt()},children:r=>(()=>{var i=jt(),a=i.firstChild,o=a.firstChild,s=o.nextSibling;s.firstChild;var c=s.nextSibling,l=c.firstChild,u=a.nextSibling,d=u.firstChild,p=d.nextSibling,m=u.nextSibling,h=m.firstChild,v=m.nextSibling;v.firstChild;var b=v.nextSibling,x=b.firstChild.nextSibling,P=x.firstChild.nextSibling,F=P.nextSibling,I=F.nextSibling,L=I.nextSibling,R=L.nextSibling.nextSibling,z=R.nextSibling,B=z.nextSibling,V=B.nextSibling.nextSibling;return(0,e.use)(e=>we=e,i),(0,e.insert)(o,()=>g(r())),(0,e.insert)(s,()=>r().repository,null),(0,e.insert)(c,()=>r().mode??r().plan?.settings.scan.mode??`Fixture`,l),(0,e.insert)(c,()=>_(r()),null),(0,e.insert)(d,()=>S(r())),(0,e.insert)(p,()=>ue(r().startedAt,r().finishedAt,q())),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return ee(r())},get children(){var t=ot();return(0,e.effect)(n=>{var i=`${S(r())}: reported phase progress`,a=y(r()).total;return i!==n.e&&(0,e.setAttribute)(t,`aria-label`,n.e=i),a!==n.t&&(0,e.setAttribute)(t,`max`,n.t=a),n},{e:void 0,t:void 0}),(0,e.effect)(()=>t.value=y(r()).done),t}}),m),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return C(r())},get children(){var t=st(),n=t.firstChild,i=n.nextSibling;return(0,e.insert)(n,()=>te(r())),(0,e.insert)(i,()=>w(r())),t}}),m),(0,e.insert)(h,()=>E(r())),(0,e.insert)(m,(0,e.createComponent)(n.Show,{get when(){return r().status===`running`},get children(){var t=ct();return t.$$click=()=>void Qt(r()),(0,e.insert)(t,()=>W()===`cancel`?`Requesting stop…`:`Cancel scan`),(0,e.effect)(()=>t.disabled=!!W()),t}}),null),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().status===`running`},get children(){var t=lt();return(0,e.insert)(t,()=>re(r())),t}}),v),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().error},get children(){var t=ze();return(0,e.insert)(t,()=>r().error),t}}),v),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().status===`cancelled`},get children(){return ut()}}),v),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>r().status===`completed`)()&&r().findings.length===0},get children(){return dt()}}),v),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().findings.length},get children(){var i=ft(),a=i.firstChild.firstChild.nextSibling,o=a.firstChild;return(0,e.insert)(a,()=>r().findings.length,o),(0,e.insert)(i,(0,e.createComponent)(n.For,{get each(){return r().findings},children:i=>(()=>{var a=Pt(),o=a.firstChild,s=o.firstChild;s.nextSibling;var c=o.nextSibling,l=c.firstChild,u=l.nextSibling;u.nextSibling;var d=c.nextSibling,p=d.nextSibling,m=p.nextSibling,h=m.nextSibling,g=h.firstChild.nextSibling;return g.nextSibling,(0,e.insert)(s,()=>i.severity),(0,e.insert)(o,()=>i.title,null),(0,e.insert)(c,()=>i.path,l),(0,e.insert)(c,()=>i.line,u),(0,e.insert)(c,()=>i.cwe,null),(0,e.insert)(d,()=>i.summary),(0,e.insert)(a,(0,e.createComponent)(n.Show,{get when(){return i.evidence},get children(){var t=Mt();return(0,e.insert)(t,()=>i.evidence),t}}),p),(0,e.insert)(m,()=>i.remediation),(0,e.insert)(h,()=>i.confidence,g),(0,e.insert)(a,(0,e.createComponent)(n.Show,{get when(){return(0,e.memo)(()=>!!(t.operations&&(t.operations.capabilities?.validation??!0)))()&&!qt()},get children(){var e=Nt();return e.$$click=()=>{T({repository:r().repository,finding:de(i)}),f(`validation`)},e}}),null),(0,e.effect)(()=>(0,e.setAttribute)(s,`data-severity`,i.severity)),a})()}),null),i}}),v),(0,e.insert)(i,(0,e.createComponent)(n.Show,{get when(){return r().status===`completed`},get children(){var t=ht(),i=t.firstChild,a=i.nextSibling,o=a.firstChild.nextSibling,s=o.nextSibling.nextSibling,c=s.firstChild,l=c.nextSibling;return(0,e.insert)(i,()=>se(r())),(0,e.insert)(o,()=>ce(r())),(0,e.insert)(s,(0,e.createComponent)(n.For,{get each(){return le(r())},children:t=>[(0,e.createComponent)(n.Show,{get when(){return t.label},get children(){var n=Ft();return(0,e.insert)(n,()=>t.label),n}}),(()=>{var n=Y();return(0,e.insert)(n,()=>t.value),n})()]}),c),(0,e.insert)(l,()=>N(r())),(0,e.insert)(s,(0,e.createComponent)(n.Show,{get when(){return r().assessment?.repositoryFindings.available},get children(){var t=pt(),n=t.firstChild,i=n.nextSibling;return i.nextSibling,(0,e.insert)(t,()=>r().assessment?.repositoryFindings.withMatchedFindingIds??`Unknown`,n),(0,e.insert)(t,()=>r().assessment?.repositoryFindings.notConfirmedInLatestScan??`Unknown`,i),t}}),null),(0,e.insert)(a,(0,e.createComponent)(n.For,{get each(){return r().assessment?.notes??[]},children:t=>(()=>{var n=It();return(0,e.insert)(n,t),n})()}),null),(0,e.insert)(a,(0,e.createComponent)(n.Show,{get when(){return r().skipped||r().assessment?.returnedFindings.informational},get children(){return mt()}}),null),t}}),v),(0,e.insert)(v,(0,e.createComponent)(n.Show,{get when(){return oe(r())},get children(){var t=gt();return(0,e.insert)(t,()=>oe(r())),t}}),null),(0,e.insert)(v,(0,e.createComponent)(n.Show,{get when(){return j(r())},get children(){var t=gt();return(0,e.insert)(t,()=>j(r())),t}}),null),(0,e.insert)(v,(0,e.createComponent)(n.Show,{get when(){return D(r()).length},get fallback(){return(()=>{var t=gt();return(0,e.insert)(t,()=>r().status===`running`?`Waiting for detailed SDK activity.`:`No detailed activity was recorded for this run.`),t})()},get children(){var t=_t();return(0,e.insert)(t,(0,e.createComponent)(n.For,{get each(){return O(r()).slice(0,4)},children:t=>(()=>{var n=Lt(),i=n.firstChild,a=i.firstChild,o=a.nextSibling,s=i.nextSibling;return(0,e.insert)(a,()=>k(t.worker)),(0,e.insert)(o,()=>t.label),(0,e.insert)(s,()=>A(t.status,r())),(0,e.effect)(r=>{var i=t.id,a=t.status;return i!==r.e&&(0,e.setAttribute)(n,`data-activity-id`,r.e=i),a!==r.t&&(0,e.setAttribute)(s,`data-status`,r.t=a),r},{e:void 0,t:void 0}),n})()})),t}}),null),(0,e.insert)(v,(0,e.createComponent)(n.Show,{get when(){return D(r()).length||r().events.length},get children(){var t=bt(),i=t.firstChild.nextSibling;return(0,e.insert)(i,(0,e.createComponent)(n.For,{get each(){return D(r())},children:t=>(()=>{var i=zt(),a=i.firstChild,o=a.nextSibling,s=o.firstChild,c=s.nextSibling;return c.nextSibling,(0,e.insert)(a,()=>Wt(t.updatedAt)),(0,e.insert)(o,()=>k(t.worker),s),(0,e.insert)(o,()=>t.label,c),(0,e.insert)(o,()=>A(t.status,r()),null),(0,e.insert)(o,(0,e.createComponent)(n.Show,{get when(){return t.paths.length},get children(){var n=Rt();return(0,e.insert)(n,()=>t.paths.join(`, `)),n}}),null),(0,e.effect)(()=>(0,e.setAttribute)(a,`datetime`,t.updatedAt)),i})()})),(0,e.insert)(t,(0,e.createComponent)(n.Show,{get when(){return r().events.length},get children(){return[vt(),(()=>{var t=yt();return(0,e.insert)(t,(0,e.createComponent)(n.For,{get each(){return[...r().events].reverse().slice(0,20)},children:t=>(()=>{var n=Bt(),r=n.firstChild,i=r.nextSibling;return(0,e.insert)(r,()=>Wt(t.time)),(0,e.insert)(i,()=>t.message),(0,e.effect)(()=>(0,e.setAttribute)(r,`datetime`,t.time)),n})()})),t})()]}}),null),t}}),null),(0,e.insert)(P,()=>E(r())),(0,e.insert)(F,()=>ie(r())),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return ae(r())},get children(){return[xt(),(()=>{var t=Y();return(0,e.insert)(t,()=>ae(r())),t})()]}}),I),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return r().telemetry?.measurement?.cost?.model},get children(){return[St(),(()=>{var t=Y();return(0,e.insert)(t,()=>r().telemetry?.measurement?.cost?.model),t})()]}}),I),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return r().telemetry?.measurement?.cost?.pricingAsOf},get children(){return[Ct(),(()=>{var t=Y();return(0,e.insert)(t,()=>r().telemetry?.measurement?.cost?.pricingAsOf),t})()]}}),I),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return r().telemetry?.measurement?.usage},children:t=>[Vt(),(()=>{var n=Ht(),r=n.firstChild,i=r.nextSibling,a=i.nextSibling.nextSibling;return a.nextSibling,(0,e.insert)(n,()=>t().inputTokens.toLocaleString(),r),(0,e.insert)(n,()=>t().cachedInputTokens.toLocaleString(),i),(0,e.insert)(n,()=>t().outputTokens.toLocaleString(),a),n})()]}),I),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return r().deepProgress},get children(){return[wt(),(()=>{var t=Y();return(0,e.insert)(t,()=>ne(r())),t})(),Tt()]}}),I),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return M(r())},get children(){return[Et(),(()=>{var t=Y();return(0,e.insert)(t,()=>M(r())),t})()]}}),I),(0,e.insert)(L,()=>r().repository),(0,e.insert)(R,()=>r().runId),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return r().scanId},get children(){return[Dt(),(()=>{var t=Ot();return(0,e.insert)(t,()=>r().scanId),t})()]}}),z),(0,e.insert)(B,()=>_(r())),(0,e.insert)(V,()=>r().mode??r().plan?.settings.scan.mode??`Fixture pattern checks`),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return r().model??r().plan?.settings.codex.model},get children(){return[kt(),(()=>{var t=Y();return(0,e.insert)(t,()=>r().model??r().plan?.settings.codex.model),t})()]}}),null),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return r().plan?.scanPrompt},get children(){return[Ye(),(()=>{var t=Y();return(0,e.insert)(t,()=>r().plan?.scanPrompt),t})()]}}),null),(0,e.insert)(x,(0,e.createComponent)(n.Show,{get when(){return r().plan?.contextFiles.length},get children(){return[Xe(),(()=>{var t=Y();return(0,e.insert)(t,()=>r().plan?.contextFiles.map(e=>e.path).join(`, `)),t})()]}}),null),(0,e.insert)(b,(0,e.createComponent)(n.Show,{get when(){return r().archived},get children(){return At()}}),null),i})()})]}}),b),(0,e.insert)(b,()=>qt()?`Fixture engine · not a security assessment`:`Codex Security · local results, OpenAI inference`,null),(0,e.insert)(b,(0,e.createComponent)(n.Show,{get when(){return o().runs.length},get children(){return[` `,`· Up to `,(0,e.memo)(()=>o().maxConcurrent),` concurrent scans per project`]}}),null),r})()}(0,e.delegateEvents)([`click`,`input`]);var Kt=(0,e.template)(`<section style=padding:24px><h2>Security</h2><p>Open a local project session to choose a scan target.</p><p>No repository is selected. Remote and unknown workspaces cannot start local scans.`),Z=t.Plugin.define({setup(r){let i={id:`security`,title:`Security`,tabbable:!0};r.add(t.Panel,{id:`security`,region:`side`,width:580,list:()=>[i],render(t){let i=(0,n.createMemo)(()=>d(t.session));return(0,e.createComponent)(n.Show,{get when(){return i()},keyed:!0,get fallback(){return Kt()},children:t=>{let i={directory:t},[a,o]=(0,n.createSignal)(),s=!1;return(0,n.onCleanup)(()=>{s=!0}),r.client.rpc(l).capabilities({},{location:i}).then(e=>{let t=e;!s&&(t.policy||t.validation)&&o(t)}).catch(()=>{}),(0,e.createComponent)(X,{directory:t,get operations(){return(0,e.memo)(()=>!!a())()?{capabilities:a(),list:()=>r.client.rpc(l).operationList({},{location:i}),prepare:e=>r.client.rpc(l).operationPrepare(e,{location:i}),start:e=>r.client.rpc(l).operationStart(e,{location:i}),answer:e=>r.client.rpc(l).operationAnswer(e,{location:i}),cancel:e=>r.client.rpc(l).operationCancel(e,{location:i})}:void 0},port:{list:()=>r.client.rpc(l).list({},{location:i}),prepare:e=>r.client.rpc(l).prepare(e,{location:i}),start:e=>r.client.rpc(l).start(e,{location:i}),cancel:e=>r.client.rpc(l).cancel(e,{location:i})}})}})}}),r.add(t.Command,{id:`open`,title:`Security: open scans`,group:`Security`,slash:{name:`security`},run:()=>{let e=r.sessions.current();e?r.layout.open(`${r.id}:security`,e):r.toast.show({title:`Open a local project session to view Security`})}})}});exports.default=Z;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Global Codex Security settings
|
|
2
|
+
|
|
3
|
+
The plugin reads exactly one optional settings file:
|
|
4
|
+
|
|
5
|
+
- `$XDG_CONFIG_HOME/opencode/codex-security.json` when XDG_CONFIG_HOME is set.
|
|
6
|
+
- Otherwise `~/.config/opencode/codex-security.json`.
|
|
7
|
+
|
|
8
|
+
There are **no project overrides**. Session tools provide repository, scoped paths, scan instructions and named context files separately. The optional per-scan `mode` (standard/deep) overrides the global mode for that scan only. Other SDK settings stay global; tool inputs do not grant authorization.
|
|
9
|
+
|
|
10
|
+
The file is read whenever a scan is prepared. Changing it invalidates an outstanding preview, so changed settings cannot silently reach an already-approved scan. No application restart is needed for setting changes.
|
|
11
|
+
|
|
12
|
+
The initial Mac file preserves the installed SDK's model/reasoning/Daybreak defaults and the prototype's USD 1 estimated stop threshold:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"codex": {
|
|
17
|
+
"model": "gpt-5.6-sol",
|
|
18
|
+
"reasoningEffort": "xhigh",
|
|
19
|
+
"daybreak": false
|
|
20
|
+
},
|
|
21
|
+
"scan": {
|
|
22
|
+
"mode": "standard",
|
|
23
|
+
"maxCostUsd": 1
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Supported options
|
|
29
|
+
|
|
30
|
+
- `codex.model`: model ID, forwarded to SDK constructor `codexOverrides.model`.
|
|
31
|
+
- `codex.reasoningEffort`: none, minimal, low, medium, high or xhigh; forwarded as `model_reasoning_effort`. Actual support depends on the selected model.
|
|
32
|
+
- `codex.daybreak`: boolean, forwarded as `codexOverrides.daybreak`.
|
|
33
|
+
- `scan.mode`: standard or deep, used only when no per-scan mode is supplied. The built-in fallback is standard.
|
|
34
|
+
- `scan.maxCostUsd`: positive estimated stop threshold. This is **not a hard billing cap**; in-flight usage may exceed it.
|
|
35
|
+
- `scan.maxTimeMinutes`: positive time limit.
|
|
36
|
+
- `scan.cyberAccessProgram`: optional standard, daybreak_blue or daybreak_red.
|
|
37
|
+
- `scan.workers`: positive integer, relevant to deep scans.
|
|
38
|
+
- `scan.subagents`: nonnegative integer, relevant to deep scans; zero is valid.
|
|
39
|
+
|
|
40
|
+
Unknown keys and invalid values fail closed. Credentials, provider URLs, environment variables, runtime executable paths and approval policies are intentionally not configurable here. Provider remains OpenAI, authentication remains ChatGPT, and owner suggestions remain disabled. Do not place secrets in this file.
|
|
41
|
+
|
|
42
|
+
## Daybreak is not a model
|
|
43
|
+
|
|
44
|
+
SDK 0.9.1 documents Daybreak as a separate toggle, available with Codex 0.163.0 or later. Set `codex.daybreak` to true to request it. Compatible OpenAI account access is still required; the toggle does not grant entitlement. A separate explicit `scan.cyberAccessProgram` selection takes precedence over the toggle. Omit that option when you want Codex to select the available program.
|
|
45
|
+
|
|
46
|
+
The plugin does not check entitlement, sign in, copy credentials or switch accounts while loading configuration. The existing Desktop Daybreak toggle does not configure the SDK's isolated runner. A real scan still needs appropriately authorized dedicated sign-in and inference. Neither model availability nor Daybreak entitlement has been verified on this account.
|
|
47
|
+
|
|
48
|
+
## Authorization and session inputs
|
|
49
|
+
|
|
50
|
+
`security_prepare_scan` uses a known local OpenCode workspace by default, or an explicit local repository path. Missing session context requires an absolute target; the plugin never guesses from its own working directory. Global settings do not select a repository or merge workspace histories. It accepts `scopePaths`, `scanPrompt`, `contextFiles` and optional `mode`. An explicit Standard/Deep request wins; otherwise mode falls back to global settings. The preview names the resolved mode and its source. It returns the target, context file names/sizes/hashes, global settings and inference/cost disclosure without starting a model.
|
|
51
|
+
|
|
52
|
+
`security_run_scan` takes the preview ID, checks OpenCode's `codex_security_scan` permission and starts only that unchanged preview. Host permission rules can honor existing applicable authorization; the plugin does not force a second confirmation after an allow decision. A denial is propagated. Use `/security-mode` in the TUI or the Desktop Scan mode selector to choose Global default, Standard or Deep without editing the global file. Choosing a mode only prepares a preview, never starts inference. The TUI `/security-scan` and Desktop review button show the preview and let the user confirm or cancel.
|
|
53
|
+
|
|
54
|
+
No whole-conversation history is copied. Context files must be existing text documentation inside the selected repository (up to eight files, 64 KiB each and 128 KiB total). Traversal, out-of-repository symlinks, credential/runtime directories and obvious credential filenames are rejected. Scoped paths guide the SDK; they are not a filesystem sandbox. The repository itself and wider SDK analysis context still require appropriate authorization.
|
|
55
|
+
|
|
56
|
+
## Verification
|
|
57
|
+
|
|
58
|
+
Offline tests verify configuration propagation into constructor and scan options, ignored project config, changed-config rejection, input boundaries, model-tool preparation, host authorization allow/deny, and no SDK creation before approval. The actual OpenCode 2.2.6 host test uses an injected mock SDK. Mock findings and test settings do not prove model/Daybreak entitlement or real scan quality.
|
|
59
|
+
|
|
60
|
+
Official SDK sources checked:
|
|
61
|
+
|
|
62
|
+
- https://github.com/openai/codex-security/blob/main/sdk/typescript/README.md
|
|
63
|
+
- https://github.com/openai/codex-security/blob/main/sdk/typescript/docs/cli.md#daybreak-opt-in
|
|
64
|
+
- Installed @openai/codex-security@0.9.1 README, docs/cli.md, config and ScanOptions declarations.
|