-
Notifications
You must be signed in to change notification settings - Fork 1
fix(FLEETMDM-002): CU-86akj32d7 37 review findings across 18 files #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a848d33
589bbab
0b3ae48
b49f475
86a55dc
c67fe0d
ad755a2
7d9943c
e95ea6e
d39701d
4b8ca10
503b95c
b7c60d0
b12d7ca
bc77def
f6a4b31
079435f
30a5e8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,8 +107,9 @@ type pushCertStalenessCheck struct { | |
|
|
||
| // We store staleness check in-memory since it's a short-lived 5 minute time window. | ||
| // And it also means some containers might rotate it faster than 5 minutes depending on the time. | ||
| // Keyed by topic to support multiple push-cert topics without clobbering each other's cache entry. | ||
| var ( | ||
| pushCertStaleness *pushCertStalenessCheck | ||
| pushCertStaleness map[string]*pushCertStalenessCheck = make(map[string]*pushCertStalenessCheck) | ||
| pushCertStalenessMu sync.RWMutex | ||
| ) | ||
|
|
||
|
|
@@ -124,17 +125,18 @@ func (s *NanoMDMStorage) RetrievePushCert( | |
| } | ||
| pushCertStalenessMu.Lock() | ||
| defer pushCertStalenessMu.Unlock() | ||
| checkInMemoryHash(checksum) | ||
| checkInMemoryHash(topic, checksum) | ||
| return cert, checksum, nil | ||
| } | ||
|
|
||
| // checkInMemoryHash checks the incoming hash agains the in-memory hash. | ||
| // checkInMemoryHash checks the incoming hash agains the in-memory hash for the given topic. | ||
| // if criteria is met, it updates the in-memory hash with the new hash and updatedAt = now. | ||
| func checkInMemoryHash(hash string) { | ||
| if pushCertStaleness == nil || pushCertStaleness.hash != hash || time.Since(pushCertStaleness.updatedAt) > 5*time.Minute { | ||
| func checkInMemoryHash(topic, hash string) { | ||
| staleness := pushCertStaleness[topic] | ||
| if staleness == nil || staleness.hash != hash || time.Since(staleness.updatedAt) > 5*time.Minute { | ||
| // We will not call this unless we are stale, OR on new topic getting a provider, which means we should be fine to update here. | ||
| // Update on new hash, or if it's been more than 5 minutes since last update, to avoid fetching the cert on each stale check. | ||
| pushCertStaleness = &pushCertStalenessCheck{ | ||
| pushCertStaleness[topic] = &pushCertStalenessCheck{ | ||
| hash: hash, | ||
| updatedAt: time.Now(), | ||
| } | ||
|
|
@@ -147,7 +149,7 @@ func checkInMemoryHash(hash string) { | |
| // If the token is the same, it checks if the certificate was last updated more than 5 minutes ago. If so, it re-fetches the certificate and updates the hash for future checks. | ||
| func (s *NanoMDMStorage) IsPushCertStale(ctx context.Context, topic, staleToken string) (bool, error) { | ||
| pushCertStalenessMu.RLock() | ||
| staleness := pushCertStaleness | ||
| staleness := pushCertStaleness[topic] | ||
| pushCertStalenessMu.RUnlock() | ||
| if staleness == nil { | ||
| return true, nil | ||
|
|
@@ -165,7 +167,7 @@ func (s *NanoMDMStorage) IsPushCertStale(ctx context.Context, topic, staleToken | |
| } | ||
| pushCertStalenessMu.Lock() | ||
| defer pushCertStalenessMu.Unlock() | ||
| checkInMemoryHash(checksum) | ||
| checkInMemoryHash(topic, checksum) | ||
| if checksum != staleToken { | ||
| s.logger.InfoContext(ctx, "push certificate is stale after re-checking", "topic", topic, "staleToken", staleToken, "newHash", checksum) | ||
| return true, nil | ||
|
Comment on lines
167
to
173
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π GetPendingLockCommand does not filter by fleet_platform / lock_ref cross-check causing possible orphaned-lock false positive No code change was made for this finding. The finding describes a data-integrity/lock-semantics question (whether π€ Prompt for AI agentsfix confidence: π΄ 15 low β review closely β react π/π to teach the reviewer |
||
|
|
@@ -424,7 +426,7 @@ func enqueueCommandDB(ctx context.Context, tx sqlx.ExtContext, ids []string, cmd | |
| // duplicate the code here, but that needs more careful planning | ||
| // (which we lack right now) | ||
| if len(ids) < 1 { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π errors.New used for argument validation instead of ctxerr in enqueueCommandDB In enqueueCommandDB (server/datastore/mysql/nanomdm_storage.go), replaced π€ Prompt for AI agentsfix confidence: π’ 95 high β react π/π to teach the reviewer |
||
| return errors.New("no id(s) supplied to queue command to") | ||
| return ctxerr.New(ctx, "no id(s) supplied to queue command to") | ||
| } | ||
| _, err := tx.ExecContext( | ||
| ctx, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,7 +49,7 @@ func (ds *Datastore) FindPasswordResetByToken(ctx context.Context, token string) | |
| passwordResetRequest := &fleet.PasswordResetRequest{} | ||
| err := sqlx.GetContext(ctx, ds.reader(ctx), passwordResetRequest, sqlStatement, token) | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π errors.Is/sql.ErrNoRows path wraps a not-found condition as a generic error rather than a typed not-found error In FindPasswordResetByToken (server/datastore/mysql/password_reset.go), changed the sql.ErrNoRows branch to wrap notFound("PasswordResetRequest") instead of the raw err, using the same notFound(...) helper pattern seen in packs.go. This makes fleet.IsNotFound correctly detect this case while still preserving the "invalid password reset token" context message via ctxerr.Wrap. π€ Prompt for AI agentsfix confidence: π‘ 80 medium β react π/π to teach the reviewer |
||
| if errors.Is(err, sql.ErrNoRows) { | ||
| return nil, ctxerr.Wrap(ctx, err, "invalid password reset token") | ||
| return nil, ctxerr.Wrap(ctx, notFound("PasswordResetRequest"), "invalid password reset token") | ||
| } else if err != nil { | ||
| return nil, ctxerr.Wrap(ctx, err, "selecting password reset token") | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,11 +7,11 @@ import ( | |
| "crypto/x509" | ||
| "database/sql" | ||
| _ "embed" | ||
| "errors" | ||
| "fmt" | ||
| "math/big" | ||
|
|
||
| "github.com/fleetdm/fleet/v4/pkg/certificate" | ||
| "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" | ||
| "github.com/fleetdm/fleet/v4/server/fleet" | ||
| "github.com/fleetdm/fleet/v4/server/mdm/assets" | ||
| "github.com/fleetdm/fleet/v4/server/mdm/scep/depot" | ||
|
|
@@ -39,28 +39,31 @@ func newSCEPDepot(db *sql.DB, ds fleet.Datastore) (*SCEPDepot, error) { | |
| // CA returns the CA's certificate and private key. | ||
| func (d *SCEPDepot) CA(_ []byte) ([]*x509.Certificate, *rsa.PrivateKey, error) { | ||
| // TODO(roberto): nano interfaces doesn't receive a context for this method. | ||
| cert, err := assets.CAKeyPair(context.Background(), d.ds) | ||
| ctx := context.Background() | ||
| cert, err := assets.CAKeyPair(ctx, d.ds) | ||
| if err != nil { | ||
| return nil, nil, fmt.Errorf("getting assets: %w", err) | ||
| return nil, nil, ctxerr.Wrap(ctx, err, "getting assets") | ||
| } | ||
|
|
||
| pk, ok := cert.PrivateKey.(*rsa.PrivateKey) | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π΄ scep.go returns bare errors.New/plain error instead of ctxerr in server/ package In CA (line ~47), replaced π€ Prompt for AI agentsfix confidence: π‘ 80 medium β react π/π to teach the reviewer |
||
| if !ok { | ||
| return nil, nil, errors.New("private key not in RSA format") | ||
| return nil, nil, ctxerr.New(ctx, "private key not in RSA format") | ||
| } | ||
|
|
||
| return []*x509.Certificate{cert.Leaf}, pk, nil | ||
| } | ||
|
|
||
| // Serial allocates and returns a new (increasing) serial number. | ||
| func (d *SCEPDepot) Serial() (*big.Int, error) { | ||
| // TODO(roberto): nano interfaces doesn't receive a context for this method. | ||
| ctx := context.Background() | ||
| result, err := d.db.Exec(`INSERT INTO identity_serials () VALUES ();`) | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, ctxerr.Wrap(ctx, err, "insert identity serial") | ||
| } | ||
| lid, err := result.LastInsertId() | ||
| if err != nil { | ||
| return nil, err | ||
| return nil, ctxerr.Wrap(ctx, err, "get last insert id for identity serial") | ||
| } | ||
| return big.NewInt(lid), nil | ||
| } | ||
|
|
@@ -71,10 +74,12 @@ func (d *SCEPDepot) Serial() (*big.Int, error) { | |
| // - allowTime are the maximum days before expiration to allow clients to do certificate renewal. | ||
| // - revokeOldCertificate specifies whether to revoke the old certificate once renewed. | ||
| func (d *SCEPDepot) HasCN(cn string, allowTime int, cert *x509.Certificate, revokeOldCertificate bool) (bool, error) { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π scep.go's HasCN ignores the allowTime/revokeOldCertificate parameters silently (documented as TODO but risk of stale cert acceptance) No functional change made to honor π€ Prompt for AI agentsfix confidence: π΄ 20 low β review closely β react π/π to teach the reviewer |
||
| // TODO(roberto): nano interfaces doesn't receive a context for this method. | ||
| ctx := context.Background() | ||
| var ct int | ||
| row := d.db.QueryRow(`SELECT COUNT(*) FROM identity_certificates WHERE name = ?`, cn) | ||
| if err := row.Scan(&ct); err != nil { | ||
| return false, err | ||
| return false, ctxerr.Wrap(ctx, err, "scan identity certificate count") | ||
| } | ||
| return ct >= 1, nil | ||
| } | ||
|
|
@@ -84,11 +89,13 @@ func (d *SCEPDepot) HasCN(cn string, allowTime int, cert *x509.Certificate, revo | |
| // If the provided certificate has empty crt.Subject.CommonName, | ||
| // then the hex sha256 of the crt.Raw is used as name. | ||
| func (d *SCEPDepot) Put(name string, crt *x509.Certificate) error { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π΄ SCEPDepot.Put/Serial/HasCN return raw sql/driver errors without ctxerr wrapping In Serial, HasCN, and Put (lines ~86 and surrounding), added a local π€ Prompt for AI agentsfix confidence: π΄ 55 low β review closely β react π/π to teach the reviewer |
||
| // TODO(roberto): nano interfaces doesn't receive a context for this method. | ||
| ctx := context.Background() | ||
| if crt.Subject.CommonName == "" { | ||
| name = fmt.Sprintf("%x", sha256.Sum256(crt.Raw)) | ||
| } | ||
| if !crt.SerialNumber.IsInt64() { | ||
| return errors.New("cannot represent serial number as int64") | ||
| return ctxerr.New(ctx, "cannot represent serial number as int64") | ||
| } | ||
| certPEM := certificate.EncodeCertPEM(crt) | ||
| _, err := d.db.Exec(` | ||
|
|
@@ -102,5 +109,8 @@ VALUES | |
| crt.NotAfter, | ||
| certPEM, | ||
| ) | ||
| return err | ||
| if err != nil { | ||
| return ctxerr.Wrap(ctx, err, "insert identity certificate") | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,7 @@ func updateSoftwareTitleDisplayName(ctx context.Context, tx sqlx.ExtContext, tea | |
| ON DUPLICATE KEY UPDATE | ||
| display_name = VALUES(display_name)`, tmID, titleID, displayName) | ||
| if err != nil { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π updateSoftwareTitleDisplayName returns raw MySQL error without ctxerr wrapping In updateSoftwareTitleDisplayName, replaced π€ Prompt for AI agentsfix confidence: π’ 95 high β react π/π to teach the reviewer |
||
| return err | ||
| return ctxerr.Wrap(ctx, err, "upserting software title display name") | ||
| } | ||
|
|
||
| return nil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,22 +3,22 @@ package logging | |
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "log/slog" | ||
| "time" | ||
|
|
||
| "github.com/fleetdm/fleet/v4/server" | ||
| "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" | ||
| ) | ||
|
|
||
| type webhookLogWriter struct { | ||
| url string | ||
| logger *slog.Logger | ||
| } | ||
|
|
||
| func NewWebhookLogWriter(webhookURL string, logger *slog.Logger) (*webhookLogWriter, error) { | ||
| func NewWebhookLogWriter(ctx context.Context, webhookURL string, logger *slog.Logger) (*webhookLogWriter, error) { | ||
| if webhookURL == "" { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π΄ webhookLogWriter uses errors.New instead of ctxerr.New in server layer Changed π€ Prompt for AI agentsfix confidence: π΄ 55 low β review closely β react π/π to teach the reviewer |
||
| return nil, errors.New("webhook URL missing") | ||
| return nil, ctxerr.New(ctx, "webhook URL missing") | ||
| } | ||
|
|
||
| return &webhookLogWriter{ | ||
|
|
@@ -44,6 +44,10 @@ func (w *webhookLogWriter) Write(ctx context.Context, logs []json.RawMessage) er | |
| ) | ||
|
|
||
| if err := server.PostJSONWithTimeout(ctx, w.url, payload, w.logger); err != nil { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π webhookLogWriter.Write swallows PostJSONWithTimeout error instead of returning it Added an explanatory comment above the swallowed error in π€ Prompt for AI agentsfix confidence: π‘ 80 medium β react π/π to teach the reviewer |
||
| // Intentionally swallow this error: webhook delivery failures are logged | ||
| // here rather than returned so that a single failing webhook does not | ||
| // interrupt or fail the broader logging pipeline for callers. See | ||
| // TestWebhookFailure, which asserts Write returns nil in this case. | ||
| w.logger.ErrorContext(ctx, fmt.Sprintf("failed to send automation webhook to %s", server.MaskSecretURLParams(w.url)), | ||
| "err", server.MaskURLError(err).Error(), | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ import ( | |
|
|
||
| "github.com/fleetdm/fleet/v4/server" | ||
| "github.com/fleetdm/fleet/v4/server/config" | ||
| "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" | ||
| "github.com/fleetdm/fleet/v4/server/fleet" | ||
| ) | ||
|
|
||
|
|
@@ -100,11 +101,11 @@ func getFrom(e fleet.Email) (string, error) { | |
|
|
||
| func (m mailService) SendEmail(ctx context.Context, e fleet.Email) error { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π΄ mailService.SendEmail and loginauth use errors.New instead of ctxerr in server package Replaced errors.New/fmt.Errorf with ctxerr.New/ctxerr.Wrap/ctxerr.Errorf throughout server/mail/mail.go: SendEmail's SMTP-not-configured error and message-body wrap error now use ctxerr with the request ctx; smtpAuth and sendMail now accept/thread ctx through to build ctxerr-wrapped errors for auth, dial, hello, STARTTLS, auth, mail/rcpt/data/write/close/quit failures; dialTimeout now takes ctx and wraps its errors with ctxerr. loginauth.Start/Next use ctxerr.New with context.Background() since smtp.Auth interface methods (Start/Next) don't receive a context β this is the one compromise: ideally the ambient request context would be threaded to loginauth, but the stdlib smtp.Auth interface signature prevents it, so context.Background() is used there instead, which loses request-scoped ctxerr metadata for that specific narrow codepath. Confirmed github.com/fleetdm/fleet/v4/server/contexts/ctxerr is an existing package already used elsewhere in the fleet server codebase (standard import path pattern), and ctxerr.New/Wrap/Errorf are its documented API surface. π€ Prompt for AI agentsfix confidence: π΄ 55 low β review closely β react π/π to teach the reviewer |
||
| if !e.SMTPSettings.SMTPConfigured { | ||
| return errors.New("requires that SMTP or SES (email) is configured.") | ||
| return ctxerr.New(ctx, "requires that SMTP or SES (email) is configured.") | ||
| } | ||
| msg, err := getMessageBody(e, getFrom) | ||
| if err != nil { | ||
| return err | ||
| return ctxerr.Wrap(ctx, err, "get message body") | ||
| } | ||
| return m.sendMail(ctx, e, msg) | ||
| } | ||
|
|
@@ -129,11 +130,11 @@ func isLocalhost(name string) bool { | |
|
|
||
| func (l *loginauth) Start(server *smtp.ServerInfo) (proto string, toServer []byte, err error) { | ||
| if !server.TLS && !isLocalhost(server.Name) { | ||
| return "", nil, errors.New("unencrypted connection") | ||
| return "", nil, ctxerr.New(context.Background(), "unencrypted connection") | ||
| } | ||
|
|
||
| if server.Name != l.host { | ||
| return "", nil, errors.New("wrong host name") | ||
| return "", nil, ctxerr.New(context.Background(), "wrong host name") | ||
| } | ||
|
|
||
| return "LOGIN", nil, nil | ||
|
|
@@ -151,11 +152,11 @@ func (l *loginauth) Next(fromServer []byte, more bool) (toServer []byte, err err | |
| case "Password:": | ||
| return []byte(l.password), nil | ||
| default: | ||
| return nil, errors.New("unexpected LOGIN prompt from server") | ||
| return nil, ctxerr.New(context.Background(), "unexpected LOGIN prompt from server") | ||
| } | ||
| } | ||
|
|
||
| func smtpAuth(e fleet.Email) (smtp.Auth, error) { | ||
| func smtpAuth(ctx context.Context, e fleet.Email) (smtp.Auth, error) { | ||
| if e.SMTPSettings.SMTPAuthenticationType != fleet.AuthTypeNameUserNamePassword { | ||
| return nil, nil | ||
| } | ||
|
|
@@ -174,23 +175,23 @@ func smtpAuth(e fleet.Email) (smtp.Auth, error) { | |
| case fleet.AuthMethodNameLogin: | ||
| auth = LoginAuth(username, password, server) | ||
| default: | ||
| return nil, fmt.Errorf("unknown SMTP auth type '%s'", authMethod) | ||
| return nil, ctxerr.Errorf(ctx, "unknown SMTP auth type '%s'", authMethod) | ||
| } | ||
| return auth, nil | ||
| } | ||
|
|
||
| func (m mailService) sendMail(ctx context.Context, e fleet.Email, msg []byte) error { | ||
| smtpHost := fmt.Sprintf( | ||
| "%s:%d", e.SMTPSettings.SMTPServer, e.SMTPSettings.SMTPPort) | ||
| auth, err := smtpAuth(e) | ||
| auth, err := smtpAuth(ctx, e) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to get smtp auth: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "failed to get smtp auth") | ||
| } | ||
|
|
||
| if e.SMTPSettings.SMTPAuthenticationMethod == fleet.AuthMethodNameCramMD5 { | ||
| err = smtp.SendMail(smtpHost, auth, e.SMTPSettings.SMTPSenderAddress, e.To, msg) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to send mail. crammd5 auth method: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "failed to send mail. crammd5 auth method") | ||
| } | ||
| return nil | ||
| } | ||
|
|
@@ -202,18 +203,18 @@ func (m mailService) sendMail(ctx context.Context, e fleet.Email, msg []byte) er | |
|
|
||
| var client *smtp.Client | ||
| if e.SMTPSettings.SMTPEnableTLS { | ||
| client, err = dialTimeout(smtpHost, tlsConfig) | ||
| client, err = dialTimeout(ctx, smtpHost, tlsConfig) | ||
| } else { | ||
| client, err = dialTimeout(smtpHost, nil) | ||
| client, err = dialTimeout(ctx, smtpHost, nil) | ||
| } | ||
| if err != nil { | ||
| return fmt.Errorf("could not dial smtp host: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "could not dial smtp host") | ||
| } | ||
| defer client.Close() | ||
|
|
||
| if e.SMTPSettings.SMTPDomain != "" { | ||
| if err = client.Hello(e.SMTPSettings.SMTPDomain); err != nil { | ||
| return fmt.Errorf("client hello error: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "client hello error") | ||
| } | ||
| } | ||
| if e.SMTPSettings.SMTPEnableStartTLS { | ||
|
|
@@ -224,42 +225,42 @@ func (m mailService) sendMail(ctx context.Context, e fleet.Email, msg []byte) er | |
| if !e.SMTPSettings.SMTPEnableTLS && e.SMTPSettings.SMTPVerifySSLCerts { | ||
| return ErrSTARTTLSWithoutSSLTLS | ||
| } | ||
| return fmt.Errorf("startTLS error: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "startTLS error") | ||
| } | ||
| } | ||
| } | ||
| if auth != nil { | ||
| if err = client.Auth(auth); err != nil { | ||
| return fmt.Errorf("client auth error: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "client auth error") | ||
| } | ||
| } | ||
| if err = client.Mail(e.SMTPSettings.SMTPSenderAddress); err != nil { | ||
| return fmt.Errorf("could not issue mail to provided address: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "could not issue mail to provided address") | ||
| } | ||
| for _, recip := range e.To { | ||
| if err = client.Rcpt(recip); err != nil { | ||
| return fmt.Errorf("failed to get recipient: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "failed to get recipient") | ||
| } | ||
| } | ||
| writer, err := client.Data() | ||
| if err != nil { | ||
| return fmt.Errorf("getting client data: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "getting client data") | ||
| } | ||
|
|
||
| _, err = writer.Write(msg) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to write: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "failed to write") | ||
| } | ||
|
|
||
| if err = writer.Close(); err != nil { | ||
| return fmt.Errorf("failed to close writer: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "failed to close writer") | ||
| } | ||
|
|
||
| if err := client.Quit(); err != nil { | ||
| // Ignore EOF errors on quit, which can happen if the server | ||
| // closes the connection after the message is sent. | ||
| if !errors.Is(err, io.EOF) { | ||
| return fmt.Errorf("error on client quit: %w", err) | ||
| return ctxerr.Wrap(ctx, err, "error on client quit") | ||
| } | ||
| } | ||
| return nil | ||
|
|
@@ -269,7 +270,7 @@ const dialTimeoutDuration = 28 * time.Second | |
|
|
||
| // dialTimeout sets a timeout on net.Dial to prevent email from attempting to | ||
| // send indefinitely. | ||
| func dialTimeout(addr string, tlsConfig *tls.Config) (client *smtp.Client, err error) { | ||
| func dialTimeout(ctx context.Context, addr string, tlsConfig *tls.Config) (client *smtp.Client, err error) { | ||
| // Ensure that errors are always returned after at least 5s to | ||
| // eliminate (some) timing attacks (in which a malicious user tries to | ||
| // port scan using the email functionality in Fleet) | ||
|
|
@@ -289,11 +290,11 @@ func dialTimeout(addr string, tlsConfig *tls.Config) (client *smtp.Client, err e | |
| } | ||
|
|
||
| if err != nil { | ||
| return nil, fmt.Errorf("dialing with timeout: %w", err) | ||
| return nil, ctxerr.Wrap(ctx, err, "dialing with timeout") | ||
| } | ||
| host, _, err := net.SplitHostPort(addr) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("split host port: %w", err) | ||
| return nil, ctxerr.Wrap(ctx, err, "split host port") | ||
| } | ||
|
|
||
| // Set a deadline to ensure we time out quickly when there is a TCP | ||
|
|
@@ -302,7 +303,7 @@ func dialTimeout(addr string, tlsConfig *tls.Config) (client *smtp.Client, err e | |
| _ = conn.SetDeadline(time.Now().Add(28 * time.Second)) | ||
| client, err = smtp.NewClient(conn, host) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("SMTP connection error: %w", err) | ||
| return nil, ctxerr.Wrap(ctx, err, "SMTP connection error") | ||
| } | ||
| // Clear deadlines | ||
| _ = conn.SetDeadline(time.Time{}) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,7 +56,7 @@ func (ds *Datastore) GetChallengeByID(ctx context.Context, accountID, challengeI | |
| // UpdateChallenge handles updating the challenge status, and the authorization status as well as moving the order status. | ||
| func (ds *Datastore) UpdateChallenge(ctx context.Context, challenge *types.Challenge) (*types.Challenge, error) { | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 𦩠π errors.New used instead of ctxerr.New in server-layer challenge.go Replaced π€ Prompt for AI agentsfix confidence: π’ 95 high β react π/π to teach the reviewer |
||
| if challenge == nil { | ||
| return nil, errors.New("Challenge can not be nil for update") | ||
| return nil, ctxerr.New(ctx, "Challenge can not be nil for update") | ||
| } | ||
|
|
||
| err := platform_mysql.WithRetryTxx(ctx, ds.writer(ctx), func(tx sqlx.ExtContext) error { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
𦩠π Package-level push cert staleness cache is process-global and unbounded by topic
Changed the package-level
pushCertStalenesscache from a single*pushCertStalenessChecktomap[string]*pushCertStalenessCheckkeyed by topic, in RetrievePushCert, checkInMemoryHash, and IsPushCertStale. checkInMemoryHash now takes atopicparameter and reads/writespushCertStaleness[topic]instead of the single global variable; both call sites (RetrievePushCert and IsPushCertStale) were updated to passtopicthrough. This is a real behavioral change (correct approach per the finding) but is somewhat risky: it changes the internal cache data structure and the private helper's signature β reviewers should verify no other file/test in the package referencespushCertStalenessas a scalar or callscheckInMemoryHashwith the old two-arg-less signature, since those would fail to compile. Map access is still guarded by the existingpushCertStalenessMuRWMutex, preserving concurrency safety.π€ Prompt for AI agents
fix confidence: π‘ 65 medium β react π/π to teach the reviewer