mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-05-09 08:32:51 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0491f1fac2 |
@@ -120,6 +120,7 @@ fahedouch
|
|||||||
fastcgi
|
fastcgi
|
||||||
FCr
|
FCr
|
||||||
fcrdns
|
fcrdns
|
||||||
|
fcvg
|
||||||
fediverse
|
fediverse
|
||||||
ffprobe
|
ffprobe
|
||||||
fhdr
|
fhdr
|
||||||
@@ -238,6 +239,7 @@ mnt
|
|||||||
Mojeek
|
Mojeek
|
||||||
mojeekbot
|
mojeekbot
|
||||||
mozilla
|
mozilla
|
||||||
|
mqvh
|
||||||
myclient
|
myclient
|
||||||
mymaster
|
mymaster
|
||||||
mypass
|
mypass
|
||||||
@@ -387,6 +389,7 @@ vnd
|
|||||||
VPS
|
VPS
|
||||||
Vultr
|
Vultr
|
||||||
WAIFU
|
WAIFU
|
||||||
|
wcg
|
||||||
weblate
|
weblate
|
||||||
webmaster
|
webmaster
|
||||||
webpage
|
webpage
|
||||||
|
|||||||
+1
-1
@@ -259,7 +259,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lg.Info("loading policy file", "fname", *policyFname)
|
lg.Info("loading policy file", "fname", *policyFname)
|
||||||
policy, err := libanubis.LoadPoliciesOrDefault(ctx, *policyFname, *challengeDifficulty, *slogLevel)
|
policy, err := libanubis.LoadPoliciesOrDefault(ctx, *policyFname, *challengeDifficulty, *slogLevel, strings.TrimSpace(*target) == "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("can't parse policy file: %v", err)
|
log.Fatalf("can't parse policy file: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<!-- This changes the project to: -->
|
<!-- This changes the project to: -->
|
||||||
|
|
||||||
|
- Patch [GHSA-6wcg-mqvh-fcvg](https://github.com/TecharoHQ/anubis/security/advisories/GHSA-6wcg-mqvh-fcvg) by containing subrequest logic to Anubis instances in subrequest mode.
|
||||||
- Move metrics server configuration to [the policy file](./admin/policies.mdx#metrics-server).
|
- Move metrics server configuration to [the policy file](./admin/policies.mdx#metrics-server).
|
||||||
- Expose [pprof endpoints](https://pkg.go.dev/net/http/pprof) on the metrics listener to enable profiling Anubis in production.
|
- Expose [pprof endpoints](https://pkg.go.dev/net/http/pprof) on the metrics listener to enable profiling Anubis in production.
|
||||||
- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463)
|
- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463)
|
||||||
@@ -24,10 +25,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
|
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
|
||||||
- Enable [metrics serving via TLS](./admin/policies.mdx#tls), including [mutual TLS (mTLS)](./admin/policies.mdx#mtls).
|
- Enable [metrics serving via TLS](./admin/policies.mdx#tls), including [mutual TLS (mTLS)](./admin/policies.mdx#mtls).
|
||||||
- Enable [HTTP basic auth](./admin/policies.mdx#http-basic-authentication) for the metrics server.
|
- Enable [HTTP basic auth](./admin/policies.mdx#http-basic-authentication) for the metrics server.
|
||||||
- Fix a bug in the dataset poisoning maze that could allow denial of service .[#1580](https://github.com/TecharoHQ/anubis/issues/1580).
|
- Fix a bug in the dataset poisoning maze that could allow denial of service [#1580](https://github.com/TecharoHQ/anubis/issues/1580).
|
||||||
- Add config option to add ASN to logs/metrics.
|
- Add config option to add ASN to logs/metrics.
|
||||||
- Log weight when issuing challenge.
|
- Log weight when issuing challenge
|
||||||
- Waste bandwidth for headless chrome using the [Prompt API](https://developer.chrome.com/docs/ai/prompt-api).
|
|
||||||
|
|
||||||
## v1.25.0: Necron
|
## v1.25.0: Necron
|
||||||
|
|
||||||
|
|||||||
@@ -595,7 +595,7 @@ func spawnAnubisWithOptions(t *testing.T, basePrefix string) string {
|
|||||||
fmt.Fprintf(w, "<html><body><span id=anubis-test>%d</span></body></html>", time.Now().Unix())
|
fmt.Fprintf(w, "<html><body><span id=anubis-test>%d</span></body></html>", time.Now().Unix())
|
||||||
})
|
})
|
||||||
|
|
||||||
policy, err := libanubis.LoadPoliciesOrDefault(t.Context(), "", anubis.DefaultDifficulty, "info")
|
policy, err := libanubis.LoadPoliciesOrDefault(t.Context(), "", anubis.DefaultDifficulty, "info", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -58,7 +58,7 @@ func loadPolicies(t *testing.T, fname string, difficulty int) *policy.ParsedConf
|
|||||||
|
|
||||||
t.Logf("loading policy file: %s", fname)
|
t.Logf("loading policy file: %s", fname)
|
||||||
|
|
||||||
anubisPolicy, err := LoadPoliciesOrDefault(ctx, fname, difficulty, "info")
|
anubisPolicy, err := LoadPoliciesOrDefault(ctx, fname, difficulty, "info", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@ func TestLoadPolicies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
if _, err := policy.ParseConfig(t.Context(), fin, fname, 4, "info"); err != nil {
|
if _, err := policy.ParseConfig(t.Context(), fin, fname, 4, "info", false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-2
@@ -55,7 +55,7 @@ type Options struct {
|
|||||||
DifficultyInJWT bool
|
DifficultyInJWT bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int, logLevel string) (*policy.ParsedConfig, error) {
|
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int, logLevel string, subrequestMode bool) (*policy.ParsedConfig, error) {
|
||||||
var fin io.ReadCloser
|
var fin io.ReadCloser
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty
|
|||||||
}
|
}
|
||||||
}(fin)
|
}(fin)
|
||||||
|
|
||||||
anubisPolicy, err := policy.ParseConfig(ctx, fin, fname, defaultDifficulty, logLevel)
|
anubisPolicy, err := policy.ParseConfig(ctx, fin, fname, defaultDifficulty, logLevel, subrequestMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't parse policy file %s: %w", fname, err)
|
return nil, fmt.Errorf("can't parse policy file %s: %w", fname, err)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestInvalidChallengeMethod(t *testing.T) {
|
func TestInvalidChallengeMethod(t *testing.T) {
|
||||||
if _, err := LoadPoliciesOrDefault(t.Context(), "testdata/invalid-challenge-method.yaml", 4, "info"); !errors.Is(err, policy.ErrChallengeRuleHasWrongAlgorithm) {
|
if _, err := LoadPoliciesOrDefault(t.Context(), "testdata/invalid-challenge-method.yaml", 4, "info", false); !errors.Is(err, policy.ErrChallengeRuleHasWrongAlgorithm) {
|
||||||
t.Fatalf("wanted error %v but got %v", policy.ErrChallengeRuleHasWrongAlgorithm, err)
|
t.Fatalf("wanted error %v but got %v", policy.ErrChallengeRuleHasWrongAlgorithm, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info"); err == nil {
|
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info", false); err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@@ -44,13 +44,13 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
t.Run("with-thoth", func(t *testing.T) {
|
t.Run("with-thoth", func(t *testing.T) {
|
||||||
ctx := thothmock.WithMockThoth(t)
|
ctx := thothmock.WithMockThoth(t)
|
||||||
if _, err := LoadPoliciesOrDefault(ctx, filepath.Join("config", "testdata", "good", st.Name()), anubis.DefaultDifficulty, "info"); err != nil {
|
if _, err := LoadPoliciesOrDefault(ctx, filepath.Join("config", "testdata", "good", st.Name()), anubis.DefaultDifficulty, "info", false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("without-thoth", func(t *testing.T) {
|
t.Run("without-thoth", func(t *testing.T) {
|
||||||
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "good", st.Name()), anubis.DefaultDifficulty, "info"); err != nil {
|
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "good", st.Name()), anubis.DefaultDifficulty, "info", false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+11
-8
@@ -94,23 +94,26 @@ func (hmc *HeaderMatchesChecker) Hash() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PathChecker struct {
|
type PathChecker struct {
|
||||||
regexp *regexp.Regexp
|
regexp *regexp.Regexp
|
||||||
hash string
|
hash string
|
||||||
|
subRequestMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPathChecker(rexStr string) (checker.Impl, error) {
|
func NewPathChecker(rexStr string, subrequestMode bool) (checker.Impl, error) {
|
||||||
rex, err := regexp.Compile(strings.TrimSpace(rexStr))
|
rex, err := regexp.Compile(strings.TrimSpace(rexStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%w: regex %s failed parse: %w", ErrMisconfiguration, rexStr, err)
|
return nil, fmt.Errorf("%w: regex %s failed parse: %w", ErrMisconfiguration, rexStr, err)
|
||||||
}
|
}
|
||||||
return &PathChecker{rex, internal.FastHash(rexStr)}, nil
|
return &PathChecker{rex, internal.FastHash(rexStr), subrequestMode}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PathChecker) Check(r *http.Request) (bool, error) {
|
func (pc *PathChecker) Check(r *http.Request) (bool, error) {
|
||||||
originalUrl := r.Header.Get("X-Original-URI")
|
if pc.subRequestMode {
|
||||||
if originalUrl != "" {
|
originalUrl := r.Header.Get("X-Original-URI")
|
||||||
if pc.regexp.MatchString(originalUrl) {
|
if originalUrl != "" {
|
||||||
return true, nil
|
if pc.regexp.MatchString(originalUrl) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+107
-2
@@ -272,8 +272,8 @@ func TestPathChecker_XOriginalURI(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Create the PathChecker
|
// Create the PathChecker in subrequest mode so X-Original-URI is honored.
|
||||||
pc, err := NewPathChecker(tt.regex)
|
pc, err := NewPathChecker(tt.regex, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !tt.expectError {
|
if !tt.expectError {
|
||||||
t.Fatalf("NewPathChecker() unexpected error: %v", err)
|
t.Fatalf("NewPathChecker() unexpected error: %v", err)
|
||||||
@@ -305,3 +305,108 @@ func TestPathChecker_XOriginalURI(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPathChecker_GHSA_6wcg_mqvh_fcvg is a regression test for
|
||||||
|
// https://github.com/TecharoHQ/anubis/security/advisories/GHSA-6wcg-mqvh-fcvg.
|
||||||
|
//
|
||||||
|
// PR https://github.com/TecharoHQ/anubis/pull/1015 added the ability for
|
||||||
|
// reverse proxies using Anubis in subrequest auth mode to look at the path
|
||||||
|
// of a request as there are many rules in the wild that rely on checking
|
||||||
|
// the path. This is how access to things like robots.txt or anything in the
|
||||||
|
// .well-known directory is unaffected by Anubis.
|
||||||
|
//
|
||||||
|
// However this logic was also enabled for non-subrequest deployments of Anubis,
|
||||||
|
// meaning that a specially crafted request could include a /.well-known/
|
||||||
|
// path in it and then get around Anubis with little effort.
|
||||||
|
//
|
||||||
|
// This fix gates the logic behind a new plumbed variable named subrequestMode
|
||||||
|
// that only fires when Anubis is running in subrequest auth mode. This
|
||||||
|
// properly contains that workaround so that the logic does not fire in
|
||||||
|
// most deployments.
|
||||||
|
func TestPathChecker_GHSA_6wcg_mqvh_fcvg(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
regex string
|
||||||
|
urlPath string
|
||||||
|
xOriginalURI string
|
||||||
|
subRequestMode bool
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default mode ignores spoofed X-Original-URI when real path matches",
|
||||||
|
regex: "^/admin/.*",
|
||||||
|
urlPath: "/admin/secret",
|
||||||
|
xOriginalURI: "/public/index",
|
||||||
|
subRequestMode: false,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default mode ignores spoofed X-Original-URI when real path does not match",
|
||||||
|
regex: "^/admin/.*",
|
||||||
|
urlPath: "/public/index",
|
||||||
|
xOriginalURI: "/admin/secret",
|
||||||
|
subRequestMode: false,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default mode without X-Original-URI matches real path",
|
||||||
|
regex: "^/admin/.*",
|
||||||
|
urlPath: "/admin/dashboard",
|
||||||
|
xOriginalURI: "",
|
||||||
|
subRequestMode: false,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subrequest mode honors X-Original-URI",
|
||||||
|
regex: "^/admin/.*",
|
||||||
|
urlPath: "/auth",
|
||||||
|
xOriginalURI: "/admin/secret",
|
||||||
|
subRequestMode: true,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subrequest mode falls back to URL.Path when X-Original-URI does not match",
|
||||||
|
regex: "^/admin/.*",
|
||||||
|
urlPath: "/admin/dashboard",
|
||||||
|
xOriginalURI: "/public/index",
|
||||||
|
subRequestMode: true,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subrequest mode with empty X-Original-URI uses URL.Path",
|
||||||
|
regex: "^/admin/.*",
|
||||||
|
urlPath: "/admin/dashboard",
|
||||||
|
xOriginalURI: "",
|
||||||
|
subRequestMode: true,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
pc, err := NewPathChecker(tt.regex, tt.subRequestMode)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewPathChecker(%q, %v) returned error: %v", tt.regex, tt.subRequestMode, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "http://example.com"+tt.urlPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("http.NewRequest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.xOriginalURI != "" {
|
||||||
|
req.Header.Set("X-Original-URI", tt.xOriginalURI)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := pc.Check(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Check() unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("Check() = %v, want %v (subRequestMode=%v, urlPath=%q, X-Original-URI=%q)",
|
||||||
|
got, tt.want, tt.subRequestMode, tt.urlPath, tt.xOriginalURI)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func newParsedConfig(orig *config.Config) *ParsedConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDifficulty int, logLevel string) (*ParsedConfig, error) {
|
func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDifficulty int, logLevel string, subrequestMode bool) (*ParsedConfig, error) {
|
||||||
c, err := config.Load(fin, fname)
|
c, err := config.Load(fin, fname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -152,7 +152,7 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.PathRegex != nil {
|
if b.PathRegex != nil {
|
||||||
c, err := NewPathChecker(*b.PathRegex)
|
c, err := NewPathChecker(*b.PathRegex, subrequestMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s path regex: %w", b.Name, err))
|
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s path regex: %w", b.Name, err))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestDefaultPolicyMustParse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
if _, err := ParseConfig(ctx, fin, "botPolicies.yaml", anubis.DefaultDifficulty, "info"); err != nil {
|
if _, err := ParseConfig(ctx, fin, "botPolicies.yaml", anubis.DefaultDifficulty, "info", false); err != nil {
|
||||||
t.Fatalf("can't parse config: %v", err)
|
t.Fatalf("can't parse config: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
ctx := thothmock.WithMockThoth(t)
|
ctx := thothmock.WithMockThoth(t)
|
||||||
if _, err := ParseConfig(ctx, fin, fin.Name(), anubis.DefaultDifficulty, "info"); err != nil {
|
if _, err := ParseConfig(ctx, fin, fin.Name(), anubis.DefaultDifficulty, "info", false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -53,7 +53,7 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
if _, err := ParseConfig(t.Context(), fin, fin.Name(), anubis.DefaultDifficulty, "info"); err != nil {
|
if _, err := ParseConfig(t.Context(), fin, fin.Name(), anubis.DefaultDifficulty, "info", false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -77,7 +77,7 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer fin.Close()
|
defer fin.Close()
|
||||||
|
|
||||||
if _, err := ParseConfig(ctx, fin, fin.Name(), anubis.DefaultDifficulty, "info"); err == nil {
|
if _, err := ParseConfig(ctx, fin, fin.Name(), anubis.DefaultDifficulty, "info", false); err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
|
|||||||
@@ -93,31 +93,12 @@ const initTranslations = async () => {
|
|||||||
translations = await loadTranslations(currentLang);
|
translations = await loadTranslations(currentLang);
|
||||||
};
|
};
|
||||||
|
|
||||||
const wasteHeadlessChromeDisk = async () => {
|
|
||||||
if (window.LanguageModel !== undefined) {
|
|
||||||
const session = await window.LanguageModel.create({
|
|
||||||
initialPrompts: [
|
|
||||||
{
|
|
||||||
role: "system",
|
|
||||||
content: "You are a helpful assistant that responds in as many words as possible. Be verbose and answer questions fully with as much detail as possible."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: "Why is the sky blue?",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Initialize translations first
|
// Initialize translations first
|
||||||
await initTranslations();
|
await initTranslations();
|
||||||
|
|
||||||
wasteHeadlessChromeDisk();
|
|
||||||
|
|
||||||
const dependencies = [
|
const dependencies = [
|
||||||
{
|
{
|
||||||
name: "Web Workers",
|
name: "Web Workers",
|
||||||
|
|||||||
Reference in New Issue
Block a user