opencode-skills-antigravity 0.0.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -22
- package/bundled-skills/007/scripts/full_audit.py +6 -4
- package/bundled-skills/007/scripts/score_calculator.py +67 -7
- package/bundled-skills/algorithmic-art/templates/viewer.html +2 -2
- package/bundled-skills/apify-actorization/SKILL.md +1 -2
- package/bundled-skills/apify-actorization/references/cli-actorization.md +4 -4
- package/bundled-skills/docs/COMMUNITY_GUIDELINES.md +1 -1
- package/bundled-skills/docs/contributors/community-guidelines.md +3 -32
- package/bundled-skills/docs/integrations/jetski-gemini-loader/loader.ts +21 -3
- package/bundled-skills/docs/maintainers/security-findings-triage-2026-03-18-addendum.md +22 -0
- package/bundled-skills/dotnet-backend-patterns/resources/implementation-playbook.md +2 -2
- package/bundled-skills/instagram/scripts/auth.py +15 -6
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +33 -1073
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package.json +7 -4
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/src/db/migrations.ts +15 -3
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/src/routes/todos.ts +85 -88
- package/bundled-skills/loki-mode/examples/todo-app-generated/frontend/package-lock.json +260 -456
- package/bundled-skills/loki-mode/examples/todo-app-generated/frontend/package.json +4 -2
- package/bundled-skills/notebooklm/scripts/auth_manager.py +17 -3
- package/bundled-skills/notebooklm/scripts/browser_session.py +11 -2
- package/bundled-skills/radix-ui-design-system/examples/README.md +1 -1
- package/bundled-skills/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts +5 -3
- package/bundled-skills/whatsapp-cloud-api/assets/boilerplate/python/app.py +21 -13
- package/bundled-skills/whatsapp-cloud-api/assets/boilerplate/python/webhook_handler.py +11 -4
- package/package.json +1 -1
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/src/db/db.ts +0 -35
- /package/bundled-skills/dotnet-backend-patterns/assets/{repository-template.cs → repository-template.cs.template} +0 -0
- /package/bundled-skills/dotnet-backend-patterns/assets/{service-template.cs → service-template.cs.template} +0 -0
- /package/bundled-skills/radix-ui-design-system/templates/{component-template.tsx → component-template.tsx.template} +0 -0
package/README.md
CHANGED
|
@@ -1,31 +1,59 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="./docs/logo.svg" alt="OpenCode Skills Antigravity"/>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<br/>
|
|
6
|
+
<br/>
|
|
7
|
+
<br/>
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/opencode-skills-antigravity)
|
|
10
|
+
[](https://www.npmjs.com/package/opencode-skills-antigravity)
|
|
11
|
+
[](./LICENSE)
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
- **Instant Deployment:** When you start OpenCode, the plugin instantly copies the pre-bundled skills to your local machine. This process works perfectly offline and ensures zero network latency during startup.
|
|
13
|
+
</div>
|
|
11
14
|
|
|
12
|
-
OpenCode
|
|
15
|
+
# OpenCode Skills Antigravity
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
> An [OpenCode CLI](https://opencode.ai/) plugin that bundles and auto-syncs the [Antigravity Awesome Skills](https://github.com/sickn33/antigravity-awesome-skills) collection — delivered instantly, with zero network latency at startup.
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
**OpenCode Skills Antigravity** bridges the OpenCode CLI with the Antigravity Awesome Skills repository. Instead of fetching skills on every startup, this plugin ships with a pre-bundled snapshot that gets copied directly to your local machine the moment OpenCode launches.
|
|
24
|
+
|
|
25
|
+
The result: skills are always fresh (synced hourly via GitHub Actions), always available (even offline), and always instant.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## How It Works
|
|
30
|
+
|
|
31
|
+
The plugin operates in two phases:
|
|
32
|
+
|
|
33
|
+
**1. Automated upstream sync (CI)**
|
|
34
|
+
|
|
35
|
+
A GitHub Actions workflow runs every hour, checking the [Antigravity Awesome Skills](https://github.com/sickn33/antigravity-awesome-skills) repository for changes. When new or updated skills are detected, the workflow:
|
|
36
|
+
|
|
37
|
+
- Re-bundles the skill files into `bundled-skills/`
|
|
38
|
+
- Bumps the package version (`patch`)
|
|
39
|
+
- Creates a tagged GitHub Release
|
|
40
|
+
- Publishes the new version to npm
|
|
41
|
+
|
|
42
|
+
**2. Local deployment (startup)**
|
|
19
43
|
|
|
20
|
-
|
|
44
|
+
When OpenCode starts, the plugin runs and copies the pre-bundled skills from the npm package to:
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
```
|
|
47
|
+
~/.config/opencode/skills/
|
|
48
|
+
```
|
|
23
49
|
|
|
24
|
-
|
|
50
|
+
No network calls, no latency, no failures. If the copy somehow fails, a silent fallback attempts to fetch via `npx antigravity-awesome-skills` in the background.
|
|
25
51
|
|
|
26
|
-
|
|
52
|
+
---
|
|
27
53
|
|
|
28
|
-
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
Add the plugin to your global OpenCode configuration file at `~/.config/opencode/opencode.json`:
|
|
29
57
|
|
|
30
58
|
```json
|
|
31
59
|
{
|
|
@@ -35,19 +63,110 @@ Edit (or create) `~/.config/opencode/opencode.json`:
|
|
|
35
63
|
}
|
|
36
64
|
```
|
|
37
65
|
|
|
38
|
-
OpenCode will automatically download the npm package on next startup via Bun
|
|
66
|
+
That's it. OpenCode will automatically download the npm package on next startup via Bun — no manual `npm install` needed.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
39
71
|
|
|
40
|
-
|
|
72
|
+
Once installed, all bundled skills are available in three ways:
|
|
41
73
|
|
|
42
|
-
|
|
74
|
+
**Explicit invocation via CLI:**
|
|
75
|
+
```bash
|
|
76
|
+
opencode run /brainstorming help me plan a new feature
|
|
77
|
+
opencode run /refactor clean up this function
|
|
78
|
+
```
|
|
43
79
|
|
|
80
|
+
**Slash commands in the OpenCode chat:**
|
|
44
81
|
```
|
|
45
|
-
|
|
82
|
+
/brainstorming
|
|
83
|
+
/refactor
|
|
84
|
+
/document
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Natural language — OpenCode picks the right skill automatically:**
|
|
88
|
+
```
|
|
89
|
+
"Help me brainstorm ideas for a REST API design"
|
|
90
|
+
"Refactor this function to be more readable"
|
|
46
91
|
```
|
|
47
92
|
|
|
48
|
-
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## CI/CD Pipeline
|
|
96
|
+
|
|
97
|
+
The release pipeline is fully automated and self-contained:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
[Hourly Cron]
|
|
101
|
+
│
|
|
102
|
+
▼
|
|
103
|
+
Auto-Sync Skills ──── no changes ───▶ (skip)
|
|
104
|
+
│
|
|
105
|
+
changes detected
|
|
106
|
+
│
|
|
107
|
+
▼
|
|
108
|
+
Bump patch version + commit + tag
|
|
109
|
+
│
|
|
110
|
+
▼
|
|
111
|
+
Create GitHub Release
|
|
112
|
+
│
|
|
113
|
+
▼
|
|
114
|
+
Publish to npm
|
|
115
|
+
│
|
|
116
|
+
▼
|
|
117
|
+
Sync main → develop
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Manual releases (minor/major/patch) can also be triggered via the **Create Release** workflow dispatch in the Actions tab.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Project Structure
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
opencode-skills-antigravity/
|
|
128
|
+
├── src/
|
|
129
|
+
│ └── index.ts # Plugin entry point — copies bundled skills on startup
|
|
130
|
+
├── bundled-skills/ # Pre-bundled skills snapshot (auto-updated by CI)
|
|
131
|
+
├── dist/ # Compiled TypeScript output
|
|
132
|
+
├── .github/
|
|
133
|
+
│ └── workflows/
|
|
134
|
+
│ ├── sync-skills.yml # Hourly skill sync + auto-publish
|
|
135
|
+
│ ├── release.yml # Manual version bump + GitHub Release
|
|
136
|
+
│ ├── publish.yml # npm publish on new release
|
|
137
|
+
│ └── merge-branch.yml # Keeps develop in sync with main
|
|
138
|
+
├── package.json
|
|
139
|
+
└── tsconfig.json
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
**Requirements:** Node.js ≥ 20, TypeScript ≥ 5
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Install dependencies
|
|
150
|
+
npm install
|
|
151
|
+
|
|
152
|
+
# Build
|
|
153
|
+
npm run build
|
|
154
|
+
|
|
155
|
+
# Output is in dist/
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The plugin is written in TypeScript and compiled to ESNext with full type declarations. It targets ES2022 and uses ESM module resolution.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Issues and pull requests are welcome at [github.com/FrancoStino/opencode-skills-antigravity](https://github.com/FrancoStino/opencode-skills-antigravity/issues).
|
|
165
|
+
|
|
166
|
+
If you'd like to contribute new skills to the upstream collection, head over to [antigravity-awesome-skills](https://github.com/sickn33/antigravity-awesome-skills) — they'll be automatically picked up and bundled here within the hour.
|
|
49
167
|
|
|
168
|
+
---
|
|
50
169
|
|
|
51
|
-
##
|
|
170
|
+
## License
|
|
52
171
|
|
|
53
172
|
MIT © [Davide Ladisa](https://www.davideladisa.it/)
|
|
@@ -1081,6 +1081,7 @@ def run_audit(
|
|
|
1081
1081
|
inj_report: dict = {"findings": [], "score": 100, "total_findings": 0}
|
|
1082
1082
|
quick_report: dict = {"findings": [], "score": 100, "total_findings": 0}
|
|
1083
1083
|
all_findings: list[dict] = []
|
|
1084
|
+
report_findings: list[dict] = []
|
|
1084
1085
|
|
|
1085
1086
|
if need_scanners:
|
|
1086
1087
|
logger.info("Running scanners for phases %s...", [p for p in phases_list if p >= 3])
|
|
@@ -1121,6 +1122,7 @@ def run_audit(
|
|
|
1121
1122
|
+ quick_report.get("findings", [])
|
|
1122
1123
|
)
|
|
1123
1124
|
all_findings = score_calculator._deduplicate_findings(raw)
|
|
1125
|
+
report_findings = score_calculator.redact_findings_for_report(all_findings)
|
|
1124
1126
|
|
|
1125
1127
|
# ------------------------------------------------------------------
|
|
1126
1128
|
# Collect source files if needed for phase 6
|
|
@@ -1142,7 +1144,7 @@ def run_audit(
|
|
|
1142
1144
|
if 2 in phases_list:
|
|
1143
1145
|
# Phase 2 benefits from phase 1 data and findings
|
|
1144
1146
|
surface = phases_data.get("phase1") or _phase1_surface_mapping(target, verbose=verbose)
|
|
1145
|
-
phases_data["phase2"] = _phase2_threat_modeling_hints(surface,
|
|
1147
|
+
phases_data["phase2"] = _phase2_threat_modeling_hints(surface, report_findings)
|
|
1146
1148
|
|
|
1147
1149
|
if 3 in phases_list:
|
|
1148
1150
|
phases_data["phase3"] = _phase3_security_checklist(
|
|
@@ -1164,10 +1166,10 @@ def run_audit(
|
|
|
1164
1166
|
)
|
|
1165
1167
|
|
|
1166
1168
|
if 4 in phases_list:
|
|
1167
|
-
phases_data["phase4"] = _phase4_red_team_scenarios(
|
|
1169
|
+
phases_data["phase4"] = _phase4_red_team_scenarios(report_findings, auth_score)
|
|
1168
1170
|
|
|
1169
1171
|
if 5 in phases_list:
|
|
1170
|
-
phases_data["phase5"] = _phase5_blue_team_recommendations(
|
|
1172
|
+
phases_data["phase5"] = _phase5_blue_team_recommendations(report_findings, auth_score)
|
|
1171
1173
|
|
|
1172
1174
|
if 6 in phases_list:
|
|
1173
1175
|
phases_data["phase6"] = _phase6_verdict(
|
|
@@ -1227,7 +1229,7 @@ def run_audit(
|
|
|
1227
1229
|
"phases_run": phases_list,
|
|
1228
1230
|
"phases": phases_data,
|
|
1229
1231
|
"total_findings": len(all_findings),
|
|
1230
|
-
"findings":
|
|
1232
|
+
"findings": report_findings,
|
|
1231
1233
|
"report_path": str(report_path),
|
|
1232
1234
|
}
|
|
1233
1235
|
|
|
@@ -64,6 +64,17 @@ import quick_scan # noqa: E402
|
|
|
64
64
|
# ---------------------------------------------------------------------------
|
|
65
65
|
logger = setup_logging("007-score-calculator")
|
|
66
66
|
|
|
67
|
+
_SENSITIVE_FINDING_KEYS = {
|
|
68
|
+
"snippet",
|
|
69
|
+
"secret",
|
|
70
|
+
"token",
|
|
71
|
+
"password",
|
|
72
|
+
"access_token",
|
|
73
|
+
"app_secret",
|
|
74
|
+
"authorization_code",
|
|
75
|
+
"client_secret",
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
|
|
68
79
|
# ---------------------------------------------------------------------------
|
|
69
80
|
# Positive-signal patterns (auth, encryption, resilience, monitoring)
|
|
@@ -360,6 +371,51 @@ def _bar(score: float, width: int = 20) -> str:
|
|
|
360
371
|
return "[" + "#" * filled + "." * (width - filled) + "]"
|
|
361
372
|
|
|
362
373
|
|
|
374
|
+
def _redact_report_value(value):
|
|
375
|
+
"""Recursively redact sensitive values from report payloads."""
|
|
376
|
+
if isinstance(value, dict):
|
|
377
|
+
return {key: _redact_report_value(value[key]) for key in value}
|
|
378
|
+
if isinstance(value, list):
|
|
379
|
+
return [_redact_report_value(item) for item in value]
|
|
380
|
+
return value
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def redact_findings_for_report(findings: list[dict]) -> list[dict]:
|
|
384
|
+
"""Return findings safe to serialize in user-facing reports."""
|
|
385
|
+
redacted: list[dict] = []
|
|
386
|
+
|
|
387
|
+
for finding in findings:
|
|
388
|
+
safe_finding: dict = {}
|
|
389
|
+
finding_type = str(finding.get("type", "")).lower()
|
|
390
|
+
|
|
391
|
+
for key, value in finding.items():
|
|
392
|
+
key_lower = key.lower()
|
|
393
|
+
if key_lower in _SENSITIVE_FINDING_KEYS:
|
|
394
|
+
safe_finding[key] = "[redacted]"
|
|
395
|
+
continue
|
|
396
|
+
if finding_type == "secret" and key_lower in {"entropy", "match", "raw", "value"}:
|
|
397
|
+
safe_finding[key] = "[redacted]"
|
|
398
|
+
continue
|
|
399
|
+
safe_finding[key] = _redact_report_value(value)
|
|
400
|
+
|
|
401
|
+
redacted.append(safe_finding)
|
|
402
|
+
|
|
403
|
+
return redacted
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def build_safe_scanner_summaries(scanner_summaries: dict[str, dict]) -> dict[str, dict]:
|
|
407
|
+
"""Return scanner summaries with primitive numeric values only."""
|
|
408
|
+
safe_summaries: dict[str, dict] = {}
|
|
409
|
+
|
|
410
|
+
for scanner_name, summary in scanner_summaries.items():
|
|
411
|
+
safe_summaries[scanner_name] = {
|
|
412
|
+
"findings": int(summary.get("findings", 0)),
|
|
413
|
+
"score": float(summary.get("score", 0)),
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return safe_summaries
|
|
417
|
+
|
|
418
|
+
|
|
363
419
|
def format_text_report(
|
|
364
420
|
target: str,
|
|
365
421
|
domain_scores: dict[str, float],
|
|
@@ -430,6 +486,7 @@ def build_json_report(
|
|
|
430
486
|
elapsed: float,
|
|
431
487
|
) -> dict:
|
|
432
488
|
"""Build a structured JSON report."""
|
|
489
|
+
safe_findings = redact_findings_for_report(all_findings)
|
|
433
490
|
return {
|
|
434
491
|
"report": "score_calculator",
|
|
435
492
|
"target": target,
|
|
@@ -444,7 +501,7 @@ def build_json_report(
|
|
|
444
501
|
"emoji": verdict["emoji"],
|
|
445
502
|
},
|
|
446
503
|
"scanner_summaries": scanner_summaries,
|
|
447
|
-
"findings":
|
|
504
|
+
"findings": safe_findings,
|
|
448
505
|
}
|
|
449
506
|
|
|
450
507
|
|
|
@@ -564,6 +621,9 @@ def run_score(
|
|
|
564
621
|
all_findings_raw = secrets_findings + dep_findings + inj_findings + quick_findings
|
|
565
622
|
all_findings = _deduplicate_findings(all_findings_raw)
|
|
566
623
|
total_findings = len(all_findings)
|
|
624
|
+
safe_findings = redact_findings_for_report(all_findings)
|
|
625
|
+
safe_total_findings = len(safe_findings)
|
|
626
|
+
safe_scanner_summaries = build_safe_scanner_summaries(scanner_summaries)
|
|
567
627
|
|
|
568
628
|
logger.info(
|
|
569
629
|
"Aggregated %d raw findings -> %d unique (deduplicated)",
|
|
@@ -613,8 +673,8 @@ def run_score(
|
|
|
613
673
|
result=f"final_score={final_score}, verdict={verdict['label']}",
|
|
614
674
|
details={
|
|
615
675
|
"domain_scores": domain_scores,
|
|
616
|
-
"total_findings":
|
|
617
|
-
"scanner_summaries":
|
|
676
|
+
"total_findings": safe_total_findings,
|
|
677
|
+
"scanner_summaries": safe_scanner_summaries,
|
|
618
678
|
"duration_seconds": round(elapsed, 3),
|
|
619
679
|
},
|
|
620
680
|
)
|
|
@@ -627,9 +687,9 @@ def run_score(
|
|
|
627
687
|
domain_scores=domain_scores,
|
|
628
688
|
final_score=final_score,
|
|
629
689
|
verdict=verdict,
|
|
630
|
-
scanner_summaries=
|
|
690
|
+
scanner_summaries=safe_scanner_summaries,
|
|
631
691
|
all_findings=all_findings,
|
|
632
|
-
total_findings=
|
|
692
|
+
total_findings=safe_total_findings,
|
|
633
693
|
elapsed=elapsed,
|
|
634
694
|
)
|
|
635
695
|
|
|
@@ -641,8 +701,8 @@ def run_score(
|
|
|
641
701
|
domain_scores=domain_scores,
|
|
642
702
|
final_score=final_score,
|
|
643
703
|
verdict=verdict,
|
|
644
|
-
scanner_summaries=
|
|
645
|
-
total_findings=
|
|
704
|
+
scanner_summaries=safe_scanner_summaries,
|
|
705
|
+
total_findings=safe_total_findings,
|
|
646
706
|
elapsed=elapsed,
|
|
647
707
|
))
|
|
648
708
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<meta charset="UTF-8">
|
|
21
21
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
22
22
|
<title>Generative Art Viewer</title>
|
|
23
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
|
|
23
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js" integrity="sha512-bcfltY+lNLlNxz38yBBm/HLaUB1gTV6I0e+fahbF9pS6roIdzUytozWdnFV8ZnM6cSAG5EbmO0ag0a/fLZSG4Q==" crossorigin="anonymous"></script>
|
|
24
24
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
25
25
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
26
26
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
|
|
@@ -596,4 +596,4 @@
|
|
|
596
596
|
});
|
|
597
597
|
</script>
|
|
598
598
|
</body>
|
|
599
|
-
</html>
|
|
599
|
+
</html>
|
|
@@ -45,10 +45,9 @@ Verify CLI is logged in:
|
|
|
45
45
|
apify info # Should return your username
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
If not logged in, check if `APIFY_TOKEN` environment variable is defined. If not, ask the user to generate one at https://console.apify.com/settings/integrations, then:
|
|
48
|
+
If not logged in, check if `APIFY_TOKEN` environment variable is defined. If not, ask the user to generate one at https://console.apify.com/settings/integrations, add it to their shell or secret manager without putting the literal token in command history, then run:
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
export APIFY_TOKEN="your_token_here"
|
|
52
51
|
apify login
|
|
53
52
|
```
|
|
54
53
|
|
|
@@ -33,12 +33,12 @@ Reference the [cli-start template Dockerfile](https://github.com/apify/actor-tem
|
|
|
33
33
|
```dockerfile
|
|
34
34
|
FROM apify/actor-node:20
|
|
35
35
|
|
|
36
|
-
# Install ubi
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
# Install ubi from a package source or a verified release artifact
|
|
37
|
+
# Example: use your base image package manager or vendor a pinned binary in the build context
|
|
38
|
+
# RUN apt-get update && apt-get install -y ubi
|
|
39
39
|
|
|
40
40
|
# Install your CLI tool from GitHub releases (example)
|
|
41
|
-
# RUN
|
|
41
|
+
# RUN install -m 0755 ./vendor/your-tool /usr/local/bin/your-tool
|
|
42
42
|
|
|
43
43
|
# Or install apify-cli and jq manually
|
|
44
44
|
RUN npm install -g apify-cli
|
|
@@ -1,33 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Community Guidelines
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone.
|
|
6
|
-
|
|
7
|
-
## Our Standards
|
|
8
|
-
|
|
9
|
-
Examples of behavior that contributes to creating a positive environment include:
|
|
10
|
-
|
|
11
|
-
- Using welcoming and inclusive language
|
|
12
|
-
- Being respectful of differing viewpoints and experiences
|
|
13
|
-
- Gracefully accepting constructive criticism
|
|
14
|
-
- Focusing on what is best for the community
|
|
15
|
-
- Showing empathy towards other community members
|
|
16
|
-
|
|
17
|
-
Examples of unacceptable behavior by participants include:
|
|
18
|
-
|
|
19
|
-
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
|
20
|
-
- Trolling, insulting/derogatory comments, and personal or political attacks
|
|
21
|
-
- Public or private harassment
|
|
22
|
-
- Publishing others' private information without explicit permission
|
|
23
|
-
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
24
|
-
|
|
25
|
-
## Enforcement
|
|
26
|
-
|
|
27
|
-
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
|
28
|
-
|
|
29
|
-
## Attribution
|
|
30
|
-
|
|
31
|
-
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1.
|
|
32
|
-
|
|
33
|
-
[homepage]: https://www.contributor-covenant.org
|
|
3
|
+
The canonical project policy now lives in the repository root at
|
|
4
|
+
[`CODE_OF_CONDUCT.md`](../../CODE_OF_CONDUCT.md).
|
|
@@ -83,16 +83,34 @@ export async function loadSkillBodies(
|
|
|
83
83
|
): Promise<string[]> {
|
|
84
84
|
const bodies: string[] = [];
|
|
85
85
|
const rootPath = path.resolve(skillsRoot);
|
|
86
|
+
const rootRealPath = await fs.promises.realpath(rootPath);
|
|
86
87
|
|
|
87
88
|
for (const meta of metas) {
|
|
88
|
-
const
|
|
89
|
-
const relativePath = path.relative(rootPath,
|
|
89
|
+
const skillDirPath = path.resolve(rootPath, meta.path);
|
|
90
|
+
const relativePath = path.relative(rootPath, skillDirPath);
|
|
90
91
|
|
|
91
92
|
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
92
93
|
throw new Error(`Skill path escapes skills root: ${meta.id}`);
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
const
|
|
96
|
+
const skillDirStat = await fs.promises.lstat(skillDirPath);
|
|
97
|
+
if (!skillDirStat.isDirectory() || skillDirStat.isSymbolicLink()) {
|
|
98
|
+
throw new Error(`Skill directory must be a regular directory inside the skills root: ${meta.id}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const fullPath = path.join(skillDirPath, "SKILL.md");
|
|
102
|
+
const skillFileStat = await fs.promises.lstat(fullPath);
|
|
103
|
+
if (!skillFileStat.isFile() || skillFileStat.isSymbolicLink()) {
|
|
104
|
+
throw new Error(`SKILL.md must be a regular file inside the skills root: ${meta.id}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const realPath = await fs.promises.realpath(fullPath);
|
|
108
|
+
const realRelativePath = path.relative(rootRealPath, realPath);
|
|
109
|
+
if (realRelativePath.startsWith("..") || path.isAbsolute(realRelativePath)) {
|
|
110
|
+
throw new Error(`SKILL.md resolves outside the skills root: ${meta.id}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const text = await fs.promises.readFile(realPath, "utf8");
|
|
96
114
|
bodies.push(text);
|
|
97
115
|
}
|
|
98
116
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Security Findings Triage Addendum (2026-03-18)
|
|
2
|
+
|
|
3
|
+
This addendum supersedes the previous Jetski loader assessment in
|
|
4
|
+
`security-findings-triage-2026-03-15.md`.
|
|
5
|
+
|
|
6
|
+
## Correction
|
|
7
|
+
|
|
8
|
+
- Finding: `Example loader trusts manifest paths, enabling file read`
|
|
9
|
+
- Path: `docs/integrations/jetski-gemini-loader/loader.ts`
|
|
10
|
+
- Previous triage status on 2026-03-15: `obsolete/not reproducible on current HEAD`
|
|
11
|
+
- Corrected assessment: the loader was still reproducible via a symlinked
|
|
12
|
+
`SKILL.md` that resolved outside `skillsRoot`. A local proof read the linked
|
|
13
|
+
file contents successfully.
|
|
14
|
+
|
|
15
|
+
## Current Status
|
|
16
|
+
|
|
17
|
+
- The loader now rejects symlinked skill directories and symlinked `SKILL.md`
|
|
18
|
+
files.
|
|
19
|
+
- The loader now resolves the real path for `SKILL.md` and rejects any target
|
|
20
|
+
outside the configured `skillsRoot`.
|
|
21
|
+
- Regression coverage lives in
|
|
22
|
+
`tools/scripts/tests/jetski_gemini_loader.test.js`.
|
|
@@ -793,7 +793,7 @@ public class ProductsApiTests : IClassFixture<WebApplicationFactory<Program>>
|
|
|
793
793
|
|
|
794
794
|
## Resources
|
|
795
795
|
|
|
796
|
-
- **assets/service-template.cs**: Complete service implementation template
|
|
797
|
-
- **assets/repository-template.cs**: Repository pattern implementation
|
|
796
|
+
- **assets/service-template.cs.template**: Complete service implementation template
|
|
797
|
+
- **assets/repository-template.cs.template**: Repository pattern implementation
|
|
798
798
|
- **references/ef-core-best-practices.md**: EF Core optimization guide
|
|
799
799
|
- **references/dapper-patterns.md**: Advanced Dapper usage patterns
|
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import argparse
|
|
21
21
|
import asyncio
|
|
22
|
+
import html
|
|
22
23
|
import json
|
|
23
24
|
import os
|
|
24
25
|
import sys
|
|
@@ -47,6 +48,15 @@ db = Database()
|
|
|
47
48
|
db.init()
|
|
48
49
|
|
|
49
50
|
|
|
51
|
+
def _mask_secret(value: str, keep: int = 4) -> str:
|
|
52
|
+
"""Mask secret-like values before showing them in terminal output."""
|
|
53
|
+
if not value:
|
|
54
|
+
return "(hidden)"
|
|
55
|
+
if len(value) <= keep:
|
|
56
|
+
return "*" * len(value)
|
|
57
|
+
return f"{value[:keep]}...masked"
|
|
58
|
+
|
|
59
|
+
|
|
50
60
|
# ── OAuth Callback Server ────────────────────────────────────────────────────
|
|
51
61
|
|
|
52
62
|
class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
|
@@ -71,7 +81,8 @@ class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
|
|
71
81
|
self.send_response(400)
|
|
72
82
|
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
73
83
|
self.end_headers()
|
|
74
|
-
|
|
84
|
+
safe_error = html.escape(error, quote=True)
|
|
85
|
+
self.wfile.write(f"<html><body><h2>Erro: {safe_error}</h2></body></html>".encode())
|
|
75
86
|
else:
|
|
76
87
|
self.send_response(404)
|
|
77
88
|
self.end_headers()
|
|
@@ -84,7 +95,7 @@ def wait_for_oauth_code() -> Optional[str]:
|
|
|
84
95
|
"""Inicia servidor local e espera pelo código de autorização."""
|
|
85
96
|
server = HTTPServer(("localhost", OAUTH_REDIRECT_PORT), OAuthCallbackHandler)
|
|
86
97
|
server.timeout = 120 # 2 minutos
|
|
87
|
-
print(
|
|
98
|
+
print("Aguardando autorização no callback OAuth local...")
|
|
88
99
|
print("(Timeout: 2 minutos)\n")
|
|
89
100
|
|
|
90
101
|
while OAuthCallbackHandler.authorization_code is None:
|
|
@@ -285,10 +296,8 @@ async def setup() -> None:
|
|
|
285
296
|
f"response_type=code"
|
|
286
297
|
)
|
|
287
298
|
|
|
288
|
-
print(
|
|
289
|
-
|
|
290
|
-
masked_url = auth_url.replace(app_id, app_id[:4] + "...masked") if app_id else auth_url
|
|
291
|
-
print(f"URL: {masked_url}\n")
|
|
299
|
+
print("\nAbrindo browser para autorização...")
|
|
300
|
+
print("A URL de autorização e o App ID não serão exibidos para evitar vazamento de credenciais.\n")
|
|
292
301
|
webbrowser.open(auth_url)
|
|
293
302
|
|
|
294
303
|
# Esperar callback
|