Bump aquasecurity/trivy-action from a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 to ed142fd0673e97e23eac54620cfb913e5ce36c25 - #2
Open
dependabot[bot] wants to merge 141 commits into
Conversation
Dependabot closed its CodeQL bump claiming the action is no longer a dependency, which is wrong: it is still used for init, autobuild and analyze in codeql.yml and for upload-sarif in quality.yml. The pin was left on v3.36.2, so the upgrade silently never happened. Pin the current v4.37.6 rather than the stale 4.36.2 the closed pull request carried. The tag is annotated, so the pin is its dereferenced commit; pinning the tag object would not resolve. The workflows already force Node 24, which is what the v4 major line expects.
…/actions/upload-artifact-7.0.1 Bump actions/upload-artifact from 6.0.0 to 7.0.1
Record the deployment that puts the console on a public hostname without opening an inbound port, including the two settings that are easy to miss and expensive to get wrong. Without PROMTACT_TRUSTED_PROXIES the connector's loopback address is treated as the client for every request, so the per-IP login backoff becomes global — one attacker's failed logins would lock out every customer — and the session cookie loses its Secure flag because X-Forwarded-Proto is only honored from a trusted proxy. Also note why the connector runs as a service rather than a container, and why 127.0.0.1 is preferred over localhost when the server listens on IPv4 only.
… atomic Two gaps in the metering and observability work. The metrics endpoint required authentication but not authorization, so the generic read rule granted it to any authenticated principal — including a customer's viewer. The counters aggregate every tenant, so a customer could read the deployment's total decision volume, capacity, backpressure and database state. That is harmless in a single-tenant install and a cross-tenant leak in the multi-tenant one now serving publicly. RequiredRoles maps /metrics to admin and the handler narrows it to the platform operator, matching how provisioning is already protected. The existing test asserted the viewer got 200, which codified the leak; it now asserts 403 for both a customer viewer and a customer's own admin. Usage metering claimed concurrent decisions increment the counter atomically, but only the no-directory degradation path was covered. Add a Postgres integration test that drives 400 concurrent increments and asserts the exact total: a lost increment is unbilled revenue and a double count overcharges a customer. It also pins the UTC billing period, separates a different month, and covers the rejected inputs.
…ss runs The sequence-aware claim asserted that calls sharing a history key influence each other while unrelated keys stay isolated, but only the memory bound was tested — the behavior the claim is actually about was unproven. Add tests that a chain escalates its own later calls, so splitting a dangerous sequence into individually harmless-looking steps does not evade the gateway, and that an unrelated chain scores identically to a clean one, so in a multi-tenant deployment one customer's activity can never degrade another customer's verdicts. Also assert a decision carries the reason, request reference, risk and metadata an audit needs. Named so the documented acceptance command for the claim picks them up: it now exercises four tests instead of one.
The gateway computed its verdict locally but then discarded it: if the store could not persist the resulting alert or action, the handler answered 500. For a deny that is worse than an outage — a caller that reads an error as "gateway unavailable" proceeds with the call that was just blocked, turning a storage incident into a bypass. Gateway records that cannot be persisted now go to a local append-only journal, fsynced per record, and the decision is served unchanged. Enforcement is not weakened while degraded: a deny stays a deny, and an approval requirement stays one — and because the pending action is not in storage it also cannot be approved during the outage, which is the safe direction. On the next successful write, and once a minute while idle, the backlog is replayed into storage; the journal preserves order and keeps anything that still fails. The journal refuses new records when full rather than rotating, since discarding the oldest security records is the loss it exists to prevent; refusals are counted. Event ingestion and the response API keep failing loudly, because there the caller needs to know nothing was stored. Failure injection uses a real write failure rather than a mock: the store is pointed at a path whose parent cannot be created. Exposed as promtact_degraded_mode, promtact_decision_journal_depth and promtact_decision_journal_dropped_total, and promoted from "intentionally not claimed" to claim 6 with its limits stated — runtime-provisioned identities still need the database to authenticate, and the journal is host-local.
The test broke the data path before constructing the server, which is not portable: reading a file through a path component that is itself a file yields ENOTDIR on Linux but an ordinary not-found on Windows. Construction therefore succeeded on Windows and failed on Linux, so the suite was green locally and red on the Linux runner. Construct the server while the path is still readable — a not-yet-existing snapshot behaves the same everywhere — and only then replace the parent directory with a regular file, so every later write fails as an unreachable database would.
…cated during an outage Closes the two capabilities the claims document still qualified. Provenance previously reached the policy engine only from the direct tool-call API. An MCP tools/call could not carry a fingerprint at all, so calling through MCP was a way around a pinned tool, and the LangChain connector never sent one. MCP now reads the claim from the request's _meta, which the protocol reserves for out-of-band annotation so it cannot be mistaken for a tool argument, with top-level keys accepted as a fallback; the connector attests per tool. A test drives the same pinned tool through both surfaces and asserts allow, deny and gate identically. Directory identities could not be verified while the database was down, so a storage incident took every runtime-provisioned agent offline. The directory adapter now keeps a bounded cache that is consulted only after a lookup fails: while the database is healthy every request still resolves against it, so a revoked key stops working immediately and the cache never delays revocation. An agent never seen before is still rejected, because failing closed is the only safe answer when the directory is unreachable. This required the store to separate "no such identity" from "directory unreachable", which it previously collapsed into a single false. The added exposure is narrow, and stated in the claim: revocation writes to the same database, so nothing can be revoked during an outage anyway; only a key revoked shortly before the outage is covered longer, bounded by --identity-cache-ttl and disabled by setting it to zero.
A decision could be enforced, audited and reported without any way to tie the three together afterwards. Every response now carries an X-Correlation-Id, the same id lands on the audit event, and a caller may supply its own so its trace joins ours. The supplied id is untrusted input that ends up in log lines, so it is accepted only when short and alphanumeric; anything else is replaced by a generated id. Without that check a caller could inject newlines and forge log entries, which matters more than usual here because these logs are security evidence. Structured logging is opt-in via --structured-logs and emits one JSON line per request with status, duration, principal, tenant and client IP. Query strings and headers are excluded because they carry tokens. The logging middleware wraps the chain from the outside so rejected requests are logged too, and the auth layer reports the resolved principal back to it.
Release the multi-tenant SaaS work that has accumulated since 0.4.0: the Postgres-backed tenant directory and platform provisioning API, the React console embedded in the binary, usage metering and Prometheus metrics, enforcement that survives a storage outage with a reconciling decision journal, tool provenance on every agent surface, the outage-only identity cache, and correlation ids with optional structured request logs.
policy.json decides which tools are approved, which principals exist and what roles they hold, which tool fingerprints are pinned and which agent identities are registered — and it was loaded from local disk without any integrity check. An attacker with file access could grant themselves admin or approve a tool, and nothing would notice. It matters most during an outage, when a restart reads that file as the only source of those facts. The policy now carries a detached HMAC signature, verified before parsing so an altered document never takes effect. With a key configured this fails closed: a missing signature is an error rather than a way to skip verification, matching how threat packs already behave. The key is deliberately separate from the threat-pack key, so a compromised detection-content key cannot also forge identities. PROMTACT_POLICY_REQUIRE_SIGNED makes a host refuse to start unverified even before a key is distributed. Without a key the behaviour is unchanged, so existing deployments keep working. Add `promtactl sign-policy`, and promote signed snapshots from "intentionally not claimed" into claim 6.
The validation timer could raise an alert but there was nowhere to send it. A receiver on the monitored host would be the wrong answer: the alert reports that something on that host is broken, so it would die exactly when needed. This Cloudflare Worker runs at the edge, independent of the machine it watches. It authenticates the sender with a required shared secret compared in constant time — an unauthenticated endpoint that pages a human is a denial-of-service target — formats the failed techniques for a human, and fans out to ntfy, Discord or Slack, each optional. Delivery failures are logged rather than returned, so a dead downstream cannot make the sender believe the alert was rejected and retry it forever.
Every endpoint is now reachable both at its original path and under /api/v1. Mirroring rather than moving is deliberate: the validation suite, the LangChain connector, the console and the agents already deployed all speak the original paths, and a hard cut would break them simultaneously for no benefit. New integrations can pin a version instead. The rewrite runs before authentication, which is the part that matters. Authorization matches concrete prefixes such as /api/admin/, so a versioned path arriving at the auth layer unrewritten would miss those rules and fall through to the generic read rule — handing customer and key metadata to any viewer. A test asserts that /api/v1/admin/tenants stays as admin-only as its original. Unversioned API responses carry RFC 8594 Deprecation and a successor Link, so integrators can find the migration target without reading a changelog; the old paths keep working and are not scheduled for removal. The OpenAPI 3.1 document is embedded and served at /api/v1/openapi.json, so it describes the deployment being talked to rather than something published elsewhere. A test asserts it still documents the enforcement surface, since an endpoint that quietly vanishes from the spec is worse than one never documented. Also disable Worker preview URLs and document the alert-path verification.
A wrangler-account.json cache file was committed with the Worker. It holds only the Cloudflare account id and name — no tokens — and the account id already appears in ordinary dashboard URLs, so nothing needs rotating and rewriting published history would be disproportionate. Local tool state still does not belong in the tree, so remove it and ignore the directory.
Requests now carry W3C Trace Context: an incoming traceparent is continued so an agent's own trace joins the enforcement decision, and the server span is echoed back on the response. Spans are exported over OTLP/HTTP JSON to any collector, Tempo or Jaeger endpoint. The wire format is implemented against the standard library rather than the OpenTelemetry SDK, which would add roughly twenty modules including protobuf and gRPC for a feature that is off unless an endpoint is configured. Export is asynchronous and bounded. A slow or dead collector adds no latency to a decision and cannot grow memory without limit; spans past the queue limit are dropped and counted in promtact_trace_spans_dropped_total.
Identifying which build serves traffic previously meant reading a symlink on the host. The revision now comes from the Go build info, so the toolchain stamps it and it cannot drift from the binary the way a hand-maintained constant can. A dirty working tree is reported as such.
People and machines were the same kind of directory record, which makes a second factor impossible to require: enforcing it would break every agent authenticating with an API key. Accounts now declare a kind, and existing rows become human, so nothing changes on upgrade. A service account cannot hold a console session. The rule is enforced twice on purpose — the directory query excludes it, and MintSession refuses it — so a future login path that resolves an identity some other way cannot reintroduce a permanent MFA-exempt door. Bearer-key authentication is untouched, since agents are the reason these accounts exist. An unrecognised or missing kind is read as human. Defaulting the other way would hand any corrupted row the exemption machines carry. Also adds the schema for TOTP enrolment, spent time steps and recovery codes, and a TOTP implementation verified against the RFC 6238 test vectors. It uses the standard library rather than a dependency: the algorithm is short, and the authentication path is where a compromised package is worth the most to an attacker.
Login now verifies the credential without issuing anything, so a TOTP or single-use recovery code can be demanded before a session exists. Three properties carry the design. The check fails closed: an unreadable enrolment state refuses the login rather than letting everyone in without a factor exactly when the database is unhealthy. A spent time step is claimed in the database, so an intercepted code cannot be replayed during the rest of its thirty-second window, and concurrent attempts elect exactly one winner. And the reasons stay distinguishable — a user told "wrong credentials" when a code was merely missing will retype their key until the backoff locks them out. Enrolment is self-service and acts only on the caller's own account, so every authenticated role can reach it. Demanding admin there would leave a viewer unable to secure their own login, which in practice means the tenant never turns MFA on. A pending enrolment stays inert until a code confirms it, so a mistyped setup cannot lock anyone out, and a confirmed one cannot be silently replaced by whoever holds a session. Deprovisioning suspends the account and revokes its keys in one transaction. Leaving live credentials behind is the usual way an offboarded identity keeps working.
An identity provider can now create, read, update and offboard directory accounts over RFC 7643/7644. The endpoints sit under /api/scim/v2 rather than at the conventional /scim/v2 root. The authentication middleware only guards /api/ and /metrics, so a top-level mount would have been served with no authentication at all — a provisioning API open to the internet. A regression test now fails if anyone adds one there. Provisioning is also admin-only for every method; under the generic read rule any viewer could have enumerated a customer's whole user directory over GET. The tenant comes from the calling credential and never from the request body, and it is part of the lookup rather than a check applied afterwards, so another customer's id resolves to "not found" instead of confirming that it exists. Roles supplied by the identity provider pass through the application allowlist, since group membership is attacker-influenceable and an unmapped value must grant nothing. Deletion suspends the account and revokes its keys instead of erasing the record: the requirement is that credentials stop working, and a row that disappears takes its audit trail with it. Only the userName equality filter is accepted. Writing a general filter language would put a parser for attacker-supplied input in front of the directory for no provisioning benefit — and the first draft here did accept a compound expression, which the test caught.
A TOTP seed has to be readable to verify a code, so unlike an API key it cannot be reduced to a hash. It is now sealed instead: each record gets its own data key, wrapped by a key-encryption key that never reaches the database, so a leaked dump decrypts nothing on its own. The integration test asserts against the raw column rather than the API that wrote it. Rotation adds a key alongside the old one and names a new primary. New records use it while existing records stay readable, so a rotation is gradual rather than a migration that must not fail halfway. Removing a key that records still reference fails loudly and names it — otherwise a botched rotation surfaces as users mysteriously losing their second factor, with nothing pointing at the cause. The key-encryption key sits behind a provider interface so a deployment can move wrapping into a KMS or HSM without touching the code that reads or writes secrets. The provider that ships holds keys in process memory from the environment; what it buys even so is separation of key from data, which is the property that matters when a backup leaks. Encryption is opt-in and prior records keep working, but a key that is configured and rejected stops startup: continuing would write plaintext while the operator believes the data is encrypted. A short key is refused at configuration time for the same reason — it produces the appearance of encryption without the substance. AES-256-GCM comes from the standard library, so nothing is added to the dependency tree on the path that protects the secrets.
Token hashes are globally unique in the schema, so the fixed constants these tests used made them pass on a fresh database and fail on every run afterwards. That is the worst shape a test can have: it looks green exactly once, and the failure appears later with no relation to the change that seems to have caused it.
The README had grown into a 600-line inventory: a 57-item feature list and every runtime flag with its explanation. That is reference material, and it was crowding out the two things a reader actually needs first — what the system does and why it is shaped that way. The inventory moves to docs/capabilities.md, the flags and file formats to docs/configuration.md, the endpoints to docs/api.md. What remains is the problem, the four choices that determine everything else, and a quick start. docs/design-decisions.md is new. It records the reasoning behind the non-obvious choices, the alternatives that were rejected and what each one costs: why enforcement is inline, why denial has to survive a storage outage, why service accounts had to exist before MFA could, why SCIM is mounted under /api/, why three features were implemented against the standard library instead of taking a dependency. It also keeps the mistakes: the metrics endpoint that leaked platform-wide counters to any viewer with a test that codified the leak, a setting that was parsed and never applied, a failure-injection test that only failed on Linux, integration tests that passed exactly once, and MCP tool calls that carried no provenance. The reasoning behind those is more useful than the fixes.
Nothing removed old release directories, so every deploy that reached a host left one behind. On a small VM that ends as a deploy failing at 3am because the disk filled with old copies of the thing being deployed. The script refuses to act unless it can positively identify the running release: the "current" symlink must resolve to a directory inside releases/. If it was replaced by a real directory or the link is dangling, readlink still succeeds and returns a path matching no release — at which point every release below the keep count looks safe to delete, including the running one. It exits instead. It keeps five releases beyond the live one by default, because deploy-release.sh rolls back to the previous release when a deploy fails; pruning to one would take the rollback target away. A keep count below two is rejected for the same reason. Dry run by default.
A deployment created before the rename carries the old table prefix, including the migration ledger. The rename has to happen before that ledger is read: the new code would otherwise find no ledger, conclude the database is empty, and create a fresh schema beside the real data — which is indistinguishable from total data loss to whoever is on call. That is why this is not a numbered migration. It runs ahead of the mechanism the numbers live in, inside the same advisory lock, and does nothing unless the old ledger exists and the new one does not. Tables and indexes move in one statement. A half-renamed schema is worse than either end state, because some tables would be found and others silently recreated empty. The test builds the situation in a throwaway schema — old prefix, populated ledger, real rows — and checks that the applied versions and the data both survive, that nothing is left behind, and that a second run changes nothing.
It existed to rename an existing deployment's tables in place, which is not how the change is being made: the database is rebuilt from the migrations and the data imported into it, so the schema is the one the migrations produce rather than the one a rename left behind. Removing it also removes a hazard. It renamed whatever lived in current_schema(), which is only correct if every connection agrees on the schema — and database/sql hands queries to a pool, so it does not. Its own test set the search path on one connection and had the rename run on another, against the wrong schema.
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 to ed142fd0673e97e23eac54620cfb913e5ce36c25. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](aquasecurity/trivy-action@a9c7b0f...ed142fd) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-version: ed142fd0673e97e23eac54620cfb913e5ce36c25 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
Author
LabelsThe following labels could not be found: Please fix the above issues or remove invalid values from |
hunterinvariants
added a commit
that referenced
this pull request
Aug 7, 2026
…/actions/upload-artifact-7.0.1 Bump actions/upload-artifact from 6.0.0 to 7.0.1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps aquasecurity/trivy-action from a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 to ed142fd0673e97e23eac54620cfb913e5ce36c25.
Commits
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)